<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>Dubbo 源码分析 – 服务导出 | 云少IT</title><meta name="keywords" content="Dubbo 源码分析 – 服务导出"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="Dubbo 源码分析 – 服务导出"><meta name="application-name" content="Dubbo 源码分析 – 服务导出"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="Dubbo 源码分析 – 服务导出"><meta property="og:url" content="https://it985.github.io/posts/bc8dae89.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="三、Dubbo 源码分析 – 服务导出1.服务导出过程本篇文章，我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件，Dubbo 在接收到事件后，会立即执行服务导出逻辑。整个逻辑大致可分为三个部分，第一是前置工作，主要用于检查参数，组装 URL。第"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://www.bing.com/th?id=OHR.BadLightning_EN-GB7102993068_UHD.jpg"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://www.bing.com/th?id=OHR.BadLightning_EN-GB7102993068_UHD.jpg"><meta name="description" content="三、Dubbo 源码分析 – 服务导出1.服务导出过程本篇文章，我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件，Dubbo 在接收到事件后，会立即执行服务导出逻辑。整个逻辑大致可分为三个部分，第一是前置工作，主要用于检查参数，组装 URL。第"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/bc8dae89"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"Dubbo 源码分析 – 服务导出",postAI:"true",pageFillDescription:"三、Dubbo 源码分析 – 服务导出, 1.服务导出过程, 2.源码分析, 2.1 前置工作, 2.1.1 检查配置, 2.1.2 多协议多注册中心导出服务, 2.1.3 组装 URL, 2.2 导出 Dubbo 服务, 2.2.1 Invoker 创建过程, 2.2.2 导出服务到本地, 2.2.3 导出服务到远程, 2.2.4 服务注册, 2.2.4.1 创建注册中心, 2.2.4.2 节点创建, 2.2.5 订阅 override 数据, 3.总结三源码分析服务导出服务导出过程本篇文章我们来研究一下导出服务的过程服务导出过程始于容器发布刷新事件在接收到事件后会立即执行服务导出逻辑整个逻辑大致可分为三个部分第一是前置工作主要用于检查参数组装第二是导出服务包含导出服务到本地和导出服务到远程两个过程第三是向注册中心注册服务用于服务发现本篇文章将会对这三个部分代码进行详细的分析在分析之前我们先来了解一下服务的导出过程支持两种服务导出方式分别延迟导出和立即导出延迟导出的入口是的方法立即导出的入口是的方法本文打算分析服务延迟导出过程因此不会分析方法下面从方法说起该方法收到容器的刷新事件后会调用方法执行服务导出操作服务导出之前要进行对一系列的配置进行检查以及生成准备工作做完随后开始导出服务首先导出到本地然后再导出到远程导出到本地就是将服务导出到中此过程比较简单导出到远程的过程则要复杂的多以协议为例类的方法将会被调用该方法主要用于创建和本身并不具备通信能力需要借助更底层的实现通信功能因此在创建实例时需要先创建或者实例并将实例作为参数传给实现类的构造方法实例创建完成后导出服务到远程的过程也就接近尾声了服务导出结束后服务消费者即可通过直联的方式消费服务当然一般我们不会使用直联的方式消费服务所以在服务导出结束后紧接着要做的事情是向注册中心注册服务此时客户端即可从注册中心发现服务以上就是服务导出的过程比较复杂下面开始分析源码从源码的角度展现整个过程源码分析一场源码分析的马拉松比赛即将开始现在我们站在赛道的起点进行热身准备本次比赛的起点位置位于的方法处好了发令枪响了我将和一些朋友从方法处出发探索服务导出的全过程下面我们来看一下方法的源码是否有延迟导出是否已导出是不是已被取消导出导出服务是一个事件响应方法该方法会在收到上下文刷新事件后执行这个方法首先会根据条件决定是否导出服务比如有些服务设置了延时导出那么此时就不应该在此处导出还有一些服务已经被导出了或者当前服务被取消导出了此时也不能再次导出相关服务注意这里的方法这个方法字面意思是是否延迟导出服务返回表示延迟导出表示不延迟导出但是该方法真实意思却并非如此当方法返回时表示无需延迟导出返回时表示需要延迟导出与字面意思恰恰相反让人觉得很奇怪下面我们来看一下这个方法的逻辑获取如果前面获取的为空这里继续获取判断是否为空或者等于暂时忽略这个条件当为空或者等于时该方法返回而不是这个方法的返回值让人有点困惑因此我重构了该方法的代码并给提了一个最终这个被合到了主分支中详细请参见现在解释一下变量含义该变量用于表示当前的容器是否支持这个值初始为在容器将自己设置到中时的方法会检测容器是否支持若支持则将置为代码就不分析了大家自行查阅了解是与框架进行整合的关键可以看做是两个框架之间的桥梁具有同样作用的类还有实现了的一些拓展接口有和这些接口我在源码分析系列文章中介绍过大家可以参考一下这里就不赘述了现在我们知道了服务导出过程的起点那么接下来我们快马加鞭继续进行比赛赛程预告下一站是服务导出的前置工作前置工作前置工作主要包含两个部分分别是配置检查以及装配在导出服务之前需要检查用户的配置是否合理或者为用户补充缺省配置配置检查完成后接下来需要根据这些配置组装在中的作用十分重要使用作为配置载体所有的拓展点都是通过获取配置这一点官方文档中有所说明采用作为配置信息的统一格式所有扩展点都通过传递携带配置信息接下来我们先来分析配置检查部分的源码随后再来分析组装部分的源码检查配置本节我们接着前面的源码向下分析前面说过方法在经过一些判断后会决定是否调用方法导出服务那么下面我们从方法开始进行分析如下获取和配置如果为则不导出服务延时导出服务立即导出服务对两个配置进行了检查并配置执行相应的动作首先是这个配置决定了是否导出服务有时候我们只是想本地启动服务进行一些调试工作这个时候我们并不希望把本地启动的服务暴露出去给别人调用此时我们就可以通过配置禁止服务导出比如见名知意了用于延迟导出服务下面我们继续分析源码这次要分析的是方法检测是否合法检测是否为空为空则新建一个并通过系统变量为其初始化下面几个语句用于检测等核心配置类对象是否为空若为空则尝试从其他配置类对象中获取相应的实例检测是否泛化服务类型设置为设置非类型对以及必要字段进行检查对合法性进行检测设置属性官方文档中没有说明不过和在功能应该是一致的用于配置本地存根获取本地存根类检测本地存根类是否可赋值给接口类若不可赋值则会抛出异常提醒使用者本地存根类类型不合法和均用于配置本地存根此处的代码和上一个分支的代码基本一致这里省略了检测各种对象是否为空为空则新建或者抛出异常导出服务表示服务提供者模型此对象中存储了和服务提供者相关的信息比如服务的配置信息服务实例等每个被导出的服务对应一个持有所有的以上就是配置检查的相关分析代码比较多需要大家耐心看一下下面对配置检查的逻辑进行简单的总结如下检测标签的属性合法性不合法则抛出异常检测等核心配置类对象是否为空若为空则尝试从其他配置类对象中获取相应的实例检测并处理泛化服务和普通服务类检测本地存根配置并进行相应的处理对等配置类进行检测为空则尝试创建若无法创建则抛出异常配置检查并非本文重点因此我不打算对方法所调用的方法进行分析方法除外在这些方法中除了方法稍微复杂一些其他方法都还好因此大家可自行进行分析好了其他的就不多说了继续向下分析多协议多注册中心导出服务允许我们使用不同的协议导出服务也允许我们向多个注册中心注册服务在方法中对多协议多注册中心进行了支持相关代码如下加载注册中心链接遍历导出每个服务上面代码比较简单首先是通过加载注册中心链接然后再遍历集合导出每个服务并在导出服务的过程中将服务注册到注册中心处下面我们先来看一下方法的逻辑检测是否存在注册中心配置类不存在则抛出异常若为空则将其设为从系统属性中加载注册中心地址判断是否合法添加中的字段信息到中添加字段信息到中解析得到列表可能包含多个注册中心因此解析得到的是一个列表将协议头设置为通过判断条件决定是否添加到中条件如下服务提供者或非服务提供者或上面代码不是很复杂包含如下逻辑检测是否存在注册中心配置类不存在则抛出异常构建参数映射集合也就是构建注册中心链接列表遍历链接列表并根据条件决定是否将其添加到中关于多协议多注册中心导出服务就先分析到这代码不是很多就不过多叙述了接下来分析组装过程组装配置检查完毕后紧接着要做的事情是根据配置以及其他一些信息组装前面说过是配置的载体通过可让的各种配置在各个模块之间传递之于犹如水之于鱼非常重要大家在阅读服务导出相关源码的过程中要注意内容的变化既然如此重要那么下面我们来了解一下组装的过程如果协议名为空或空串则将协议名变量设置为添加版本时间戳以及进程号等信息到中通过反射将对象的字段信息到中为集合中存储了标签的配置信息这段代码用于添加配置到中代码太长待会单独分析检测是否为并根据检测结果向中添加不同的信息为接口生成包裹类中包含了接口的详细信息比如接口方法名数组字段信息等添加方法名到中如果包含多个方法名则用逗号隔开比如将逗号作为分隔符连接方法名并将连接后的字符串放入中添加到中判断协议名是否为获取上下文路径获取和组装省略无关代码上面的代码首先是将一些信息比如版本时间戳方法名以及各种配置对象的字段信息放入到中中的内容将作为的查询字符串构建好后紧接着是获取上下文路径主机名以及端口号等信息最后将和主机名等数据传给构造方法创建对象需要注意的是这里出现的并非而是上面省略了一段代码这里简单分析一下这段代码用于检测标签中的配置信息并将相关配置添加到中代码如下为集合中存储了标签的配置信息添加对象的字段信息到中键方法名属性名比如存储对应的键检测是否为若是则设置重试次数为获取列表检测属性是否为空或者空串分支比对方法名查找目标方法检测中的属性与方法参数列表中的参数名称是否一致不一致则抛出异常分支添加字段信息到中键前缀方法名比如分支从参数类型列表中查找类型名称为的参数用户未配置属性但配置了属性且分支添加字段信息到中上面这段代码循环和分支嵌套太多导致层次太深不利于阅读需要耐心看一下大家在看这段代码时注意把几个重要的条件分支找出来只要理解了这几个分支的意图就可以弄懂这段代码我在上面代码中用符号标识出了个重要的分支下面用伪代码解释一下这几个分支的含义获取列表遍历列表不为也不为空串分支通过反射获取的方法列表遍历方法列表比对方法名查找目标方法通过反射获取目标方法的参数类型数组分支从数组中获取下标处的元素检测的名称与中的属性是否一致添加字段信息到中或抛出异常分支遍历参数类型数组查找类型的参数添加字段信息到中分支添加字段信息到中在本节分析的源码中这个方法出现的次数比较多该方法用于将对象字段信息添加到中实现上则是通过反射获取目标对象的方法并调用该方法获取属性值然后再通过方法名解析出属性名比如从方法名中可解析出属性如果用户传入了属性名前缀此时需要将属性名加入前缀内容最后将属性名属性值键值对存入到中就行了限于篇幅原因这里就不分析方法的源码了大家请自行分析导出服务前置工作做完接下来就可以进行服务导出工作服务导出分为导出到本地和导出到远程在深入分析服务导出源码前我们先来从宏观层面上看一下服务导出逻辑如下省略无关代码加载并生成配置如果则什么都不做导出到本地导出到远程加载监视器链接将监视器链接作为参数添加到中为服务提供类生成仅用于持有和导出服务并生成不存在注册中心仅导出服务上面代码根据中的参数决定服务导出方式分别如下不导出服务导出到本地导出到远程不管是导出到本地还是远程进行服务导出之前均需要先创建这是一个很重要的步骤因此接下来我会先分析的创建过程创建过程在中是一个非常重要的模型在服务提供端以及服务引用端均会出现官方文档中对进行了说明这里引用一下是实体域它是的核心模型其它模型都向它靠扰或转换成它它代表一个可执行体可向它发起调用它有可能是一个本地的实现也可能是一个远程的实现也可能一个集群实现既然如此重要那么我们很有必要搞清楚的用途是由创建而来默认的实现类是下面我们到代码中探索的创建过程如下为目标类创建创建匿名类对象并实现方法调用的方法最终会调用目标方法如上创建了一个继承自类的匿名对象并覆写了抽象方法覆写后的逻辑比较简单仅是将调用请求转发给了类的方法用于包裹目标类是一个抽象类仅可通过方法创建子类在创建子类的过程中子类代码生成逻辑会对方法传入的对象进行解析拿到诸如类方法类成员变量等信息以及生成方法代码和其他一些方法代码代码生成完毕后通过生成对象最后再通过反射创建实例相关的代码如下访存缓存未命中创建写入缓存方法只是包含了一些缓存操作逻辑非重点下面我们重点关注方法检测是否为私有类型若是则抛出异常用于存储方法代码用于存储方法代码用于存储方法代码生成类型转换代码及异常捕捉代码比如用于存储成员变量名和类型用于存储方法描述信息可理解为方法签名及实例为方法名列表用于存储定义在当前类中的方法的名称分割线获取访问级别的字段并为所有字段生成条件判断语句忽略关键字或修饰的变量生成条件判断及赋值语句比如生成条件判断及返回语句比如存储字段名字段类型键值对到中分割线检测中是否包含在当前类中声明的方法忽略中定义的方法生成方法名判断语句示例如下生成运行时传入参数的数量与方法的参数列表长度判断语句示例如下检测方法是否存在重载情况条件为方法对象不同方法名相同对重载方法进行处理考虑下面的方法方法名相同参数列表长度也相同因此不能仅通过这两项判断两个方法是否相等需要进一步判断方法的参数类型添加完成方法判断语句此时生成的方法可能如下已格式化根据返回值类型生成目标方法调用语句添加当前方法判断条件代码生成完毕示例代码如下已格式化添加方法名到集合中检测当前方法是否在中被声明的若是则将当前方法名添加到中添加异常捕捉语句添加异常抛出代码分割线处理方法匹配以开头的方法获取属性名生成属性判断以及返回语句示例如下匹配以开头的方法生成属性判断以及返回语句示例如下匹配以开头的方法生成属性判断以及调用语句示例如下添加异常抛出代码分割线创建类生成器设置类名及超类添加默认构造方法添加字段添加方法代码生成类设置字段值创建实例上面代码很长大家耐心看一下我在上面代码中做了大量的注释并按功能对代码进行了分块以帮助大家理解代码逻辑下面对这段代码进行讲解首先我们把目光移到分割线之上的代码这段代码主要用于进行一些初始化操作比如创建以及等变量以及向中添加方法定义和类型类型转换代码接下来是分割线到分割线之间的代码这段代码用于为级别的字段生成条件判断取值与赋值代码这段代码不是很难看懂就不多说了继续向下看分割线和分隔线之间的代码用于为定义在当前类中的方法生成判断语句和方法调用语句因为需要对方法重载进行校验因此到这这段代码看起来有点复杂不过耐心开一下也不是很难理解接下来是分割线和分隔线之间的代码这段代码用于处理以及以开头的方法处理方式是通过正则表达式获取方法类型以及属性名之后为属性名生成判断语句然后为方法生成调用语句最后我们再来看一下分隔线以下的代码这段代码通过为刚刚生成的代码构建类并通过反射创建对象是自己封装的该类的核心是的重载方法该方法通过构建这里就不分析方法了大家请自行分析阅读类代码需要对框架有所了解关于大家如果不熟悉请自行查阅资料本节不打算介绍相关内容好了关于类生成过程就分析到这如果大家看的不是很明白可以单独为创建单元测试然后单步调试并将生成的代码拷贝出来格式化后再进行观察和理解好了本节先到这导出服务到本地本节我们来看一下服务导出相关的代码按照代码执行顺序本节先来分析导出服务到本地的过程相关代码如下如果的协议头等于说明已经导出到本地了无需再次导出设置协议头为创建并导出服务这里的会在运行时调用的方法方法比较简单首先根据协议头决定是否导出服务若需导出则创建一个新的并将协议头主机名以及端口设置成新的值然后创建并调用的方法导出服务下面我们来看一下的方法都做了哪些事情创建如上的方法仅创建了一个无其他逻辑到此导出服务到本地就分析完了接下来我们继续分析导出服务到远程的过程导出服务到远程与导出服务到本地相比导出服务到远程的过程要复杂不少其包含了服务导出与服务注册两个过程这两个过程涉及到了大量的调用因此比较复杂不过不管再难我们都要看一下万一看懂了呢按照代码执行顺序本节先来分析服务导出逻辑服务注册逻辑将在下一节进行分析下面开始分析我们把目光移动到的方法上导出服务获取注册中心以注册中心为例得到的示例如下根据加载实现类比如获取已注册的服务提供者比如获取参数向服务提供者与消费者注册表中注册服务提供者根据的值决定是否注册服务向注册中心注册服务获取订阅比如创建监听器向注册中心进行订阅数据创建并返回上面代码看起来比较复杂主要做如下一些操作调用导出服务向注册中心注册服务向注册中心进行订阅数据创建并返回在以上操作中除了创建并返回没啥难度外其他几步操作都不是很简单这其中导出服务和注册服务是本章要重点分析的逻辑订阅数据这个是非重点内容后面会简单介绍一下下面开始本节的分析先来分析方法的逻辑如下访问缓存创建为委托类对象调用的方法导出服务写缓存上面的代码是典型的双重检查这个大家应该都知道接下来我们把重点放在的方法上假设运行时协议为此处的会在运行时加载并调用的方法我们目光转移到的方法上相关分析如下获取服务标识理解成服务坐标也行由服务组名服务名服务版本号以及端口组成比如创建将键值对放入缓存中以下代码应该和本地存根有关代码不难看懂但具体用途暂时不清楚先忽略省略日志打印代码启动服务器优化序列化如上我们重点关注的创建以及方法其他逻辑看不懂也没关系不影响理解服务导出过程另外的代码比较简单就不分析了下面分析方法获取并将其作为服务器实例的用于标识当前的服务器实例访问缓存创建服务器实例服务器已创建则根据中的配置重置服务器如上在同一台机器上单网卡同一个端口上仅允许启动一个服务器实例若某个端口上已有服务器实例此时则调用方法重置服务器的一些配置考虑到篇幅问题关于服务器实例重置的代码就不分析了接下来分析服务器实例的创建过程如下添加心跳检测配置到中获取参数默认为通过检测是否存在参数所代表的拓展不存在则抛出异常添加编码解码器参数创建获取参数可指定获取所有的实现类名称集合比如检测当前所支持的实现类名称列表中是否包含所表示的若不包含则抛出异常如上包含三个核心的操作第一是检测是否存在参数所代表的拓展不存在则抛出异常第二是创建服务器实例第三是检测是否支持参数所表示的拓展不存在也是抛出异常两次检测操作所对应的代码比较直白了无需多说但创建服务器的操作目前还不是很清晰我们继续往下看获取默认为紧接着调用的方法创建实例上面代码比较简单就不多说了下面看一下的方法创建实例该方法包含了多步操作本别如下的方法包含的逻辑比较多但目前我们仅需关心的方法逻辑即可该方法的代码如下如果元素数量大于则创建分发器获取自适应实例并调用实例方法如上方法获取的是在运行时动态创建的类名为也就是自适应拓展类我在上一篇文章中详细分析了自适应拓展类的生成过程对自适应拓展类不了解的同学可以参考我之前的文章这里不再赘述会在运行时根据传入的参数决定加载什么类型的默认为下面我们继续跟下去这次分析的是的方法创建这里仅有一句创建的代码没啥好讲的我们继续向下看调用父类构造方法调用父类构造方法这里就不用跟进去了没什么复杂逻辑获取和端口设置为获取最大可接受连接数调用模板方法启动服务器上面多数代码为赋值代码不需要多讲我们重点关注抽象方法该方法需要子类实现下面回到中创建和线程池创建设置绑定到指定的和端口上以上就是创建的过程默认使用的是基于版本实现的比较老了因此中另外提供了版本的大家可在使用的过程中按需进行配置到此关于服务导出的过程就分析完了整个过程比较复杂大家在分析的过程中耐心一些并且多写进行进行调试以便能够更好的理解代码逻辑好了本节内容先到这里接下来分析服务导出的另一块逻辑服务注册服务注册本节我们来分析服务注册过程服务注册操作对于来说不是必需的通过服务直连的方式就可以绕过注册中心但通常我们不会这么做直连方式不利于服务治理仅推荐在测试环境测试服务时使用对于来说注册中心虽不是必需但却是必要的因此关于注册中心以及服务注册相关逻辑我们也需要搞懂本节内容以注册中心作为分析目标其他类型注册中心大家可自行分析下面从服务注册的入口方法开始分析我们把目光再次移到的方法上如下导出服务省略其他代码注册服务订阅数据省略部分代码的方法包含了服务导出注册以及数据订阅等逻辑其中服务导出逻辑上一节已经分析过了本节将分析服务注册逻辑数据订阅逻辑将在下一节进行分析下面开始本节的分析相关代码如下获取注册服务方法包含两步操作第一步是获取注册中心实例第二步是向注册中心注册服务接下来我分两节内容对这两步操作进行分析按照顺序先来分析获取注册中心的逻辑创建注册中心本节内容以注册中心为例进行分析下面先来看一下方法的源码这个方法由实现如下访问缓存缓存未命中创建实例写入缓存如上方法先访问缓存缓存未命中则调用创建然后写入缓存这里的是一个模板方法由具体的子类实现因此下面我们到中探究一番由在运行时注入类型为创建的方法仅包含一句代码无需解释继续跟下去获取组名默认为创建客户端默认为添加状态监听器在上面的代码代码中我们重点关注的方法调用这个方法用于创建客户端创建好客户端意味着注册中心的创建过程就结束了不过显然我们不能就此停止难道大家没有兴趣了解一下客户端的创建过程吗如果有那么继续向下看没有的话直接跳到下一节那我接着分析了前面说过这里的类型为自适应拓展类因此方法会在被调用时决定加载什么类型的拓展默认为下面我们到中看一看创建上面方法仅用于创建实例没什么好说的继续往下看创建构造器构建实例添加监听器启动客户端构造方法主要用于创建和启动实例以上基本上都是框架的代码大家如果对框架不是很了解可以参考官方文档并写点跑跑本节分析了实例的创建过程整个过程并不是很复杂大家在看完分析后可以自行调试以加深印象现在注册中心实例创建好了接下来要做的事情是向注册中心注册服务我们继续往下看节点创建以为例所谓的服务注册本质上是将服务配置数据写入到的某个路径的节点下为了验证这个说法下面我们将官方提供提供的实例跑起来然后通过可视化客户端查看节点数据可以看到这个服务对应的配置信息存储在中最终被注册到了节点下搞懂了服务注册的本质那么接下来我们就可以去阅读服务注册的代码了服务注册的接口为这个方法定义在抽象类中方法代码如下模板方法由子类实现获取参数若将会直接抛出异常记录注册失败的链接如上我们重点关注方法调用即可其他的代码先忽略方法是一个模板方法因此我们到子类中进行分析如下通过客户端创建节点节点路径由方法生成路径格式如下比如如上在中调用了客户端创建服务节点节点路径由方法生成该方法逻辑不难理解就不分析了接下来分析方法如下如果要创建的节点类型非临时节点那么这里要检测节点是否存在递归创建上一级路径根据的值创建临时或持久节点上面方法先是通过递归创建当前节点的上一级路径然后再根据的值决定创建临时还是持久节点和这两个方法都比较简单这里简单分析其中的一个如下通过框架创建节点好了到此关于服务注册的过程就分析完了整个过程可简单总结为先创建注册中心实例之后再通过注册中心实例注册服务本节先到这接下来分析数据订阅过程订阅数据订阅数据对应的代码我粗略看了一遍这部分代码的主要目的是为了在服务配置发生变化时重新导出服务具体的使用场景应该当我们通过管理后台修改了服务配置后得到服务配置被修改的通知然后重新导出服务这个使用场景只是猜测我并未进行过验证如果大家有兴趣可以自行验证数据订阅相关代码也不是很少考虑到文章篇幅问题以及重要性遂决定不对此逻辑进行详细的分析如果大家有兴趣可自行分析总结本篇文章详细分析了服务导出过程包括配置检测组装创建过程导出服务以及注册服务等等篇幅比较大需要大家耐心阅读对于这篇文章我建议大家当成一个工具书使用需要的时候跳到指定章节看一下通读可能会有点累由于文章篇幅比较大因此可能会隐藏一些我没意识到的错误若大家在阅读的过程中发现了错误还请指出如果能够不吝赐教那就更好了先在这里说声谢谢",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2020-09-21 22:29:54",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"></span></div></div><h1 class="post-title" itemprop="name headline">Dubbo 源码分析 – 服务导出</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2020-09-21T14:29:54.000Z" title="发表于 2020-09-21 22:29:54">2020-09-21</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2020-09-21T14:29:54.000Z" title="更新于 2020-09-21 22:29:54">2020-09-21</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">14.4k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>59分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="Dubbo 源码分析 – 服务导出"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://www.bing.com/th?id=OHR.SealRiver_EN-IN3503931734_UHD.jpg"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/bc8dae89.html"><header><h1 id="CrawlerTitle" itemprop="name headline">Dubbo 源码分析 – 服务导出</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2020-09-21T14:29:54.000Z" title="发表于 2020-09-21 22:29:54">2020-09-21</time><time itemprop="dateCreated datePublished" datetime="2020-09-21T14:29:54.000Z" title="更新于 2020-09-21 22:29:54">2020-09-21</time></header><h1 id="三、Dubbo-源码分析-–-服务导出"><a href="#三、Dubbo-源码分析-–-服务导出" class="headerlink" title="三、Dubbo 源码分析 – 服务导出"></a>三、Dubbo 源码分析 – 服务导出</h1><h2 id="1-服务导出过程"><a href="#1-服务导出过程" class="headerlink" title="1.服务导出过程"></a>1.服务导出过程</h2><p>本篇文章，我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件，Dubbo 在接收到事件后，会立即执行服务导出逻辑。整个逻辑大致可分为三个部分，第一是前置工作，主要用于检查参数，组装 URL。第二是导出服务，包含导出服务到本地 (JVM)，和导出服务到远程两个过程。第三是向注册中心注册服务，用于服务发现。本篇文章将会对这三个部分代码进行详细的分析，在分析之前，我们先来了解一下服务的导出过程。</p><p>Dubbo 支持两种服务导出方式，分别延迟导出和立即导出。延迟导出的入口是 ServiceBean 的 afterPropertiesSet 方法，立即导出的入口是 ServiceBean 的 onApplicationEvent 方法。本文打算分析服务延迟导出过程，因此不会分析 afterPropertiesSet 方法。下面从 onApplicationEvent 方法说起，该方法收到 Spring 容器的刷新事件后，会调用 export 方法执行服务导出操作。服务导出之前，要进行对一系列的配置进行检查，以及生成 URL。准备工作做完，随后开始导出服务。首先导出到本地，然后再导出到远程。导出到本地就是将服务导出到 JVM 中，此过程比较简单。导出到远程的过程则要复杂的多，以 dubbo 协议为例，DubboProtocol 类的 export 方法将会被调用。该方法主要用于创建 Exporter 和 ExchangeServer。ExchangeServer 本身并不具备通信能力，需要借助更底层的 Server 实现通信功能。因此，在创建 ExchangeServer 实例时，需要先创建 NettyServer 或者 MinaServer 实例，并将实例作为参数传给 ExchangeServer 实现类的构造方法。ExchangeServer 实例创建完成后，导出服务到远程的过程也就接近尾声了。服务导出结束后，服务消费者即可通过直联的方式消费服务。当然，一般我们不会使用直联的方式消费服务。所以，在服务导出结束后，紧接着要做的事情是向注册中心注册服务。此时，客户端即可从注册中心发现服务。</p><p>以上就是 Dubbo 服务导出的过程，比较复杂。下面开始分析源码，从源码的角度展现整个过程。</p><h2 id="2-源码分析"><a href="#2-源码分析" class="headerlink" title="2.源码分析"></a>2.源码分析</h2><p>一场 Dubbo 源码分析的马拉松比赛即将开始，现在我们站在赛道的起点进行热身准备。本次比赛的起点位置位于 ServiceBean 的 onApplicationEvent 方法处。好了，发令枪响了，我将和一些朋友从 onApplicationEvent 方法处出发，探索 Dubbo 服务导出的全过程。下面我们来看一下 onApplicationEvent 方法的源码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onApplicationEvent</span><span class="params">(ContextRefreshedEvent event)</span> &#123;</span><br><span class="line">    <span class="comment">// 是否有延迟导出 &amp;&amp; 是否已导出 &amp;&amp; 是不是已被取消导出</span></span><br><span class="line">    <span class="keyword">if</span> (isDelay() &amp;&amp; !isExported() &amp;&amp; !isUnexported()) &#123;</span><br><span class="line">        <span class="comment">// 导出服务</span></span><br><span class="line">        export();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>onApplicationEvent 是一个事件响应方法，该方法会在收到 Spring 上下文刷新事件后执行。这个方法首先会根据条件决定是否导出服务，比如有些服务设置了延时导出，那么此时就不应该在此处导出。还有一些服务已经被导出了，或者当前服务被取消导出了，此时也不能再次导出相关服务。注意这里的 isDelay 方法，这个方法字面意思是“是否延迟导出服务”，返回 true 表示延迟导出，false 表示不延迟导出。但是该方法真实意思却并非如此，当方法返回 true 时，表示无需延迟导出。返回 false 时，表示需要延迟导出。与字面意思恰恰相反，让人觉得很奇怪。下面我们来看一下这个方法的逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// -☆- ServiceBean</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isDelay</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 获取 delay</span></span><br><span class="line">    <span class="type">Integer</span> <span class="variable">delay</span> <span class="operator">=</span> getDelay();</span><br><span class="line">    <span class="type">ProviderConfig</span> <span class="variable">provider</span> <span class="operator">=</span> getProvider();</span><br><span class="line">    <span class="keyword">if</span> (delay == <span class="literal">null</span> &amp;&amp; provider != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果前面获取的 delay 为空，这里继续获取</span></span><br><span class="line">        delay = provider.getDelay();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断 delay 是否为空，或者等于 -1</span></span><br><span class="line">    <span class="keyword">return</span> supportedApplicationListener &amp;&amp; (delay == <span class="literal">null</span> || delay == -<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>暂时忽略 supportedApplicationListener 这个条件，当 delay 为空，或者等于-1时，该方法返回 true，而不是 false。这个方法的返回值让人有点困惑，因此我重构了该方法的代码，并给 Dubbo 提了一个 Pull Request，最终这个 PR 被合到了 Dubbo 主分支中。详细请参见 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/pull/2686">Dubbo #2686</a>。</p><p>现在解释一下 supportedApplicationListener 变量含义，该变量用于表示当前的 Spring 容器是否支持 ApplicationListener，这个值初始为 false。在 Spring 容器将自己设置到 ServiceBean 中时，ServiceBean 的 setApplicationContext 方法会检测 Spring 容器是否支持 ApplicationListener。若支持，则将 supportedApplicationListener 置为 true。代码就不分析了，大家自行查阅了解。</p><p>ServiceBean 是 Dubbo 与 Spring 框架进行整合的关键，可以看做是两个框架之间的桥梁。具有同样作用的类还有 ReferenceBean。ServiceBean 实现了 Spring 的一些拓展接口，有 FactoryBean、ApplicationContextAware、ApplicationListener、DisposableBean 和 BeanNameAware。这些接口我在 [Spring 源码分析系列][Spring]文章中介绍过，大家可以参考一下，这里就不赘述了。</p><p>现在我们知道了 Dubbo 服务导出过程的起点。那么接下来，我们快马加鞭，继续进行比赛。赛程预告，下一站是“服务导出的前置工作”。</p><h3 id="2-1-前置工作"><a href="#2-1-前置工作" class="headerlink" title="2.1 前置工作"></a>2.1 前置工作</h3><p>前置工作主要包含两个部分，分别是配置检查，以及 URL 装配。在导出服务之前，Dubbo 需要检查用户的配置是否合理，或者为用户补充缺省配置。配置检查完成后，接下来需要根据这些配置组装 URL。在 Dubbo 中，URL 的作用十分重要。Dubbo 使用 URL 作为配置载体，所有的拓展点都是通过 URL 获取配置。这一点，官方文档中有所说明。</p><blockquote><p>采用 URL 作为配置信息的统一格式，所有扩展点都通过传递 URL 携带配置信息。</p></blockquote><p>接下来，我们先来分析配置检查部分的源码，随后再来分析 URL 组装部分的源码。</p><h4 id="2-1-1-检查配置"><a href="#2-1-1-检查配置" class="headerlink" title="2.1.1 检查配置"></a>2.1.1 检查配置</h4><p>本节我们接着前面的源码向下分析，前面说过 onApplicationEvent 方法在经过一些判断后，会决定是否调用 export 方法导出服务。那么下面我们从 export 方法开始进行分析，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">export</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (provider != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 获取 export 和 delay 配置</span></span><br><span class="line">        <span class="keyword">if</span> (export == <span class="literal">null</span>) &#123;</span><br><span class="line">            export = provider.getExport();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (delay == <span class="literal">null</span>) &#123;</span><br><span class="line">            delay = provider.getDelay();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果 export 为 false，则不导出服务</span></span><br><span class="line">    <span class="keyword">if</span> (export != <span class="literal">null</span> &amp;&amp; !export) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (delay != <span class="literal">null</span> &amp;&amp; delay &gt; <span class="number">0</span>) &#123;    <span class="comment">// delay &gt; 0，延时导出服务</span></span><br><span class="line">        delayExportExecutor.schedule(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                doExport();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, delay, TimeUnit.MILLISECONDS);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 立即导出服务</span></span><br><span class="line">        doExport();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>export 对两个配置进行了检查，并配置执行相应的动作。首先是 export，这个配置决定了是否导出服务。有时候我们只是想本地启动服务进行一些调试工作，这个时候我们并不希望把本地启动的服务暴露出去给别人调用。此时，我们就可以通过配置 export 禁止服务导出，比如：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dubbo:provider</span> <span class="attr">export</span>=<span class="string">&quot;false&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>delay 见名知意了，用于延迟导出服务。下面，我们继续分析源码，这次要分析的是 doExport 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">doExport</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (unexported) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already unexported!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (exported) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    exported = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 检测 interfaceName 是否合法</span></span><br><span class="line">    <span class="keyword">if</span> (interfaceName == <span class="literal">null</span> || interfaceName.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;interface not allow null!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检测 provider 是否为空，为空则新建一个，并通过系统变量为其初始化</span></span><br><span class="line">    checkDefault();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空，</span></span><br><span class="line">    <span class="comment">// 若为空，则尝试从其他配置类对象中获取相应的实例。</span></span><br><span class="line">    <span class="keyword">if</span> (provider != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (application == <span class="literal">null</span>) &#123;</span><br><span class="line">            application = provider.getApplication();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">module</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">module</span> = provider.getModule();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;...&#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;...&#125;</span><br><span class="line">        <span class="keyword">if</span> (protocols == <span class="literal">null</span>) &#123;...&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">module</span> != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;</span><br><span class="line">            registries = <span class="keyword">module</span>.getRegistries();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;...&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (application != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;</span><br><span class="line">            registries = application.getRegistries();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;...&#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测 ref 是否泛化服务类型</span></span><br><span class="line">    <span class="keyword">if</span> (ref <span class="keyword">instanceof</span> GenericService) &#123;</span><br><span class="line">        <span class="comment">// 设置 interfaceClass 为 GenericService.class</span></span><br><span class="line">        interfaceClass = GenericService.class;</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(generic)) &#123;</span><br><span class="line">            <span class="comment">// 设置 generic = &quot;true&quot;</span></span><br><span class="line">            generic = Boolean.TRUE.toString();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;    <span class="comment">// ref 非 GenericService 类型</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            interfaceClass = Class.forName(interfaceName, <span class="literal">true</span>, Thread.currentThread()</span><br><span class="line">                    .getContextClassLoader());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e.getMessage(), e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 对 interfaceClass，以及 &lt;dubbo:method&gt; 必要字段进行检查</span></span><br><span class="line">        checkInterfaceAndMethods(interfaceClass, methods);</span><br><span class="line">        <span class="comment">// 对 ref 合法性进行检测</span></span><br><span class="line">        checkRef();</span><br><span class="line">        <span class="comment">// 设置 generic = &quot;false&quot;</span></span><br><span class="line">        generic = Boolean.FALSE.toString();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// local 属性 Dubbo 官方文档中没有说明，不过 local 和 stub 在功能应该是一致的，用于配置本地存根</span></span><br><span class="line">    <span class="keyword">if</span> (local != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="string">&quot;true&quot;</span>.equals(local)) &#123;</span><br><span class="line">            local = interfaceName + <span class="string">&quot;Local&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        Class&lt;?&gt; localClass;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 获取本地存根类</span></span><br><span class="line">            localClass = ClassHelper.forNameWithThreadContextClassLoader(local);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e.getMessage(), e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 检测本地存根类是否可赋值给接口类，若不可赋值则会抛出异常，提醒使用者本地存根类类型不合法</span></span><br><span class="line">        <span class="keyword">if</span> (!interfaceClass.isAssignableFrom(localClass)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;The local implementation class &quot;</span> + localClass.getName() + <span class="string">&quot; not implement interface &quot;</span> + interfaceName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// stub 和 local 均用于配置本地存根</span></span><br><span class="line">    <span class="keyword">if</span> (stub != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 此处的代码和上一个 if 分支的代码基本一致，这里省略了</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测各种对象是否为空，为空则新建，或者抛出异常</span></span><br><span class="line">    checkApplication();</span><br><span class="line">    checkRegistry();</span><br><span class="line">    checkProtocol();</span><br><span class="line">    appendProperties(<span class="built_in">this</span>);</span><br><span class="line">    checkStubAndMock(interfaceClass);</span><br><span class="line">    <span class="keyword">if</span> (path == <span class="literal">null</span> || path.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        path = interfaceName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 导出服务</span></span><br><span class="line">    doExportUrls();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ProviderModel 表示服务提供者模型，此对象中存储了和服务提供者相关的信息。</span></span><br><span class="line">    <span class="comment">// 比如服务的配置信息，服务实例等。每个被导出的服务对应一个 ProviderModel。</span></span><br><span class="line">    <span class="comment">// ApplicationModel 持有所有的 ProviderModel。</span></span><br><span class="line">    <span class="type">ProviderModel</span> <span class="variable">providerModel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ProviderModel</span>(getUniqueServiceName(), <span class="built_in">this</span>, ref);</span><br><span class="line">    ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上就是配置检查的相关分析，代码比较多，需要大家耐心看一下。下面对配置检查的逻辑进行简单的总结，如下：</p><ol><li>检测 标签的 interface 属性合法性，不合法则抛出异常</li><li>检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空，若为空，则尝试从其他配置类对象中获取相应的实例。</li><li>检测并处理泛化服务和普通服务类</li><li>检测本地存根配置，并进行相应的处理</li><li>对 ApplicationConfig、RegistryConfig 等配置类进行检测，为空则尝试创建，若无法创建则抛出异常</li></ol><p>配置检查并非本文重点，因此我不打算对 doExport 方法所调用的方法进行分析（doExportUrls 方法除外）。在这些方法中，除了 appendProperties 方法稍微复杂一些，其他方法都还好。因此，大家可自行进行分析。好了，其他的就不多说了，继续向下分析。</p><h4 id="2-1-2-多协议多注册中心导出服务"><a href="#2-1-2-多协议多注册中心导出服务" class="headerlink" title="2.1.2 多协议多注册中心导出服务"></a>2.1.2 多协议多注册中心导出服务</h4><p>Dubbo 允许我们使用不同的协议导出服务，也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议，多注册中心进行了支持。相关代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doExportUrls</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 加载注册中心链接</span></span><br><span class="line">    List&lt;URL&gt; registryURLs = loadRegistries(<span class="literal">true</span>);</span><br><span class="line">    <span class="comment">// 遍历 protocols，导出每个服务</span></span><br><span class="line">    <span class="keyword">for</span> (ProtocolConfig protocolConfig : protocols) &#123;</span><br><span class="line">        doExportUrlsFor1Protocol(protocolConfig, registryURLs);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码比较简单，首先是通过 loadRegistries 加载注册中心链接，然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中，将服务注册到注册中心处。下面，我们先来看一下 loadRegistries 方法的逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> List&lt;URL&gt; <span class="title function_">loadRegistries</span><span class="params">(<span class="type">boolean</span> provider)</span> &#123;</span><br><span class="line">    <span class="comment">// 检测是否存在注册中心配置类，不存在则抛出异常</span></span><br><span class="line">    checkRegistry();</span><br><span class="line">    List&lt;URL&gt; registryList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;URL&gt;();</span><br><span class="line">    <span class="keyword">if</span> (registries != <span class="literal">null</span> &amp;&amp; !registries.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (RegistryConfig config : registries) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">address</span> <span class="operator">=</span> config.getAddress();</span><br><span class="line">            <span class="keyword">if</span> (address == <span class="literal">null</span> || address.length() == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 若 address 为空，则将其设为 0.0.0.0</span></span><br><span class="line">                address = Constants.ANYHOST_VALUE;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 从系统属性中加载注册中心地址</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">sysaddress</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;dubbo.registry.address&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (sysaddress != <span class="literal">null</span> &amp;&amp; sysaddress.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                address = sysaddress;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 判断 address 是否合法</span></span><br><span class="line">            <span class="keyword">if</span> (address.length() &gt; <span class="number">0</span> &amp;&amp; !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) &#123;</span><br><span class="line">                Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line">                <span class="comment">// 添加 ApplicationConfig 中的字段信息到 map 中</span></span><br><span class="line">                appendParameters(map, application);</span><br><span class="line">                <span class="comment">// 添加 RegistryConfig 字段信息到 map 中</span></span><br><span class="line">                appendParameters(map, config);</span><br><span class="line">                map.put(<span class="string">&quot;path&quot;</span>, RegistryService.class.getName());</span><br><span class="line">                map.put(<span class="string">&quot;dubbo&quot;</span>, Version.getProtocolVersion());</span><br><span class="line">                map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));</span><br><span class="line">                <span class="keyword">if</span> (ConfigUtils.getPid() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (!map.containsKey(<span class="string">&quot;protocol&quot;</span>)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension(<span class="string">&quot;remote&quot;</span>)) &#123;</span><br><span class="line">                        map.put(<span class="string">&quot;protocol&quot;</span>, <span class="string">&quot;remote&quot;</span>);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        map.put(<span class="string">&quot;protocol&quot;</span>, <span class="string">&quot;dubbo&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 解析得到 URL 列表，address 可能包含多个注册中心 ip，</span></span><br><span class="line">                <span class="comment">// 因此解析得到的是一个 URL 列表</span></span><br><span class="line">                List&lt;URL&gt; urls = UrlUtils.parseURLs(address, map);</span><br><span class="line">                <span class="keyword">for</span> (URL url : urls) &#123;</span><br><span class="line">                    url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());</span><br><span class="line">                    <span class="comment">// 将 URL 协议头设置为 registry</span></span><br><span class="line">                    url = url.setProtocol(Constants.REGISTRY_PROTOCOL);</span><br><span class="line">                    <span class="comment">// 通过判断条件，决定是否添加 url 到 registryList 中，条件如下：</span></span><br><span class="line">                    <span class="comment">// (服务提供者 &amp;&amp; register = true 或 null) </span></span><br><span class="line">                    <span class="comment">//    || (非服务提供者 &amp;&amp; subscribe = true 或 null)</span></span><br><span class="line">                    <span class="keyword">if</span> ((provider &amp;&amp; url.getParameter(Constants.REGISTER_KEY, <span class="literal">true</span>))</span><br><span class="line">                            || (!provider &amp;&amp; url.getParameter(Constants.SUBSCRIBE_KEY, <span class="literal">true</span>))) &#123;</span><br><span class="line">                        registryList.add(url);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> registryList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码不是很复杂，包含如下逻辑：</p><ol><li>检测是否存在注册中心配置类，不存在则抛出异常</li><li>构建参数映射集合，也就是 map</li><li>构建注册中心链接列表</li><li>遍历链接列表，并根据条件决定是否将其添加到 registryList 中</li></ol><p>关于多协议多注册中心导出服务就先分析到这，代码不是很多，就不过多叙述了。接下来分析 URL 组装过程。</p><h4 id="2-1-3-组装-URL"><a href="#2-1-3-组装-URL" class="headerlink" title="2.1.3 组装 URL"></a>2.1.3 组装 URL</h4><p>配置检查完毕后，紧接着要做的事情是根据配置，以及其他一些信息组装 URL。前面说过，URL 是 Dubbo 配置的载体，通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo，犹如水之于鱼，非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中，要注意 URL 内容的变化。既然 URL 如此重要，那么下面我们来了解一下 URL 组装的过程。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doExportUrlsFor1Protocol</span><span class="params">(ProtocolConfig protocolConfig, List&lt;URL&gt; registryURLs)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> protocolConfig.getName();</span><br><span class="line">    <span class="comment">// 如果协议名为空，或空串，则将协议名变量设置为 dubbo</span></span><br><span class="line">    <span class="keyword">if</span> (name == <span class="literal">null</span> || name.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        name = <span class="string">&quot;dubbo&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line">    <span class="comment">// 添加 side、版本、时间戳以及进程号等信息到 map 中</span></span><br><span class="line">    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);</span><br><span class="line">    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());</span><br><span class="line">    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));</span><br><span class="line">    <span class="keyword">if</span> (ConfigUtils.getPid() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过反射将对象的字段信息到 map 中</span></span><br><span class="line">    appendParameters(map, application);</span><br><span class="line">    appendParameters(map, <span class="keyword">module</span>);</span><br><span class="line">    appendParameters(map, provider, Constants.DEFAULT_KEY);</span><br><span class="line">    appendParameters(map, protocolConfig);</span><br><span class="line">    appendParameters(map, <span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// methods 为 MethodConfig 集合，MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span></span><br><span class="line">    <span class="keyword">if</span> (methods != <span class="literal">null</span> &amp;&amp; !methods.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 这段代码用于添加 Callback 配置到 map 中，代码太长，待会单独分析</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测 generic 是否为 &quot;true&quot;，并根据检测结果向 map 中添加不同的信息</span></span><br><span class="line">    <span class="keyword">if</span> (ProtocolUtils.isGeneric(generic)) &#123;</span><br><span class="line">        map.put(Constants.GENERIC_KEY, generic);</span><br><span class="line">        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">revision</span> <span class="operator">=</span> Version.getVersion(interfaceClass, version);</span><br><span class="line">        <span class="keyword">if</span> (revision != <span class="literal">null</span> &amp;&amp; revision.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            map.put(<span class="string">&quot;revision&quot;</span>, revision);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 为接口生成包裹类 Wrapper，Wrapper 中包含了接口的详细信息，比如接口方法名数组，字段信息等</span></span><br><span class="line">        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();</span><br><span class="line">        <span class="comment">// 添加方法名到 map 中，如果包含多个方法名，则用逗号隔开，比如 method = init,destroy</span></span><br><span class="line">        <span class="keyword">if</span> (methods.length == <span class="number">0</span>) &#123;</span><br><span class="line">            logger.warn(<span class="string">&quot;NO method found in service interface ...&quot;</span>);</span><br><span class="line">            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 将逗号作为分隔符连接方法名，并将连接后的字符串放入 map 中</span></span><br><span class="line">            map.put(Constants.METHODS_KEY, StringUtils.join(<span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;String&gt;(Arrays.asList(methods)), <span class="string">&quot;,&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加 token 到 map 中</span></span><br><span class="line">    <span class="keyword">if</span> (!ConfigUtils.isEmpty(token)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (ConfigUtils.isDefault(token)) &#123;</span><br><span class="line">            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            map.put(Constants.TOKEN_KEY, token);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断协议名是否为 injvm</span></span><br><span class="line">    <span class="keyword">if</span> (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) &#123;</span><br><span class="line">        protocolConfig.setRegister(<span class="literal">false</span>);</span><br><span class="line">        map.put(<span class="string">&quot;notify&quot;</span>, <span class="string">&quot;false&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取上下文路径</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">contextPath</span> <span class="operator">=</span> protocolConfig.getContextpath();</span><br><span class="line">    <span class="keyword">if</span> ((contextPath == <span class="literal">null</span> || contextPath.length() == <span class="number">0</span>) &amp;&amp; provider != <span class="literal">null</span>) &#123;</span><br><span class="line">        contextPath = provider.getContextpath();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 host 和 port</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">host</span> <span class="operator">=</span> <span class="built_in">this</span>.findConfigedHosts(protocolConfig, registryURLs, map);</span><br><span class="line">    <span class="type">Integer</span> <span class="variable">port</span> <span class="operator">=</span> <span class="built_in">this</span>.findConfigedPorts(protocolConfig, name, map);</span><br><span class="line">    <span class="comment">// 组装 URL</span></span><br><span class="line">    <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(name, host, port, (contextPath == <span class="literal">null</span> || contextPath.length() == <span class="number">0</span> ? <span class="string">&quot;&quot;</span> : contextPath + <span class="string">&quot;/&quot;</span>) + path, map);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码首先是将一些信息，比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中，map 中的内容将作为 URL 的查询字符串。构建好 map 后，紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是，这里出现的 URL 并非 java.net.URL，而是 com.alibaba.dubbo.common.URL。</p><p>上面省略了一段代码，这里简单分析一下。这段代码用于检测 标签中的配置信息，并将相关配置添加到 map 中。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doExportUrlsFor1Protocol</span><span class="params">(ProtocolConfig protocolConfig, List&lt;URL&gt; registryURLs)</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// methods 为 MethodConfig 集合，MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span></span><br><span class="line">    <span class="keyword">if</span> (methods != <span class="literal">null</span> &amp;&amp; !methods.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (MethodConfig method : methods) &#123;</span><br><span class="line">            <span class="comment">// 添加 MethodConfig 对象的字段信息到 map 中，键 = 方法名.属性名。</span></span><br><span class="line">            <span class="comment">// 比如存储 &lt;dubbo:method name=&quot;sayHello&quot; retries=&quot;2&quot;&gt; 对应的 MethodConfig，</span></span><br><span class="line">            <span class="comment">// 键 = sayHello.retries，map = &#123;&quot;sayHello.retries&quot;: 2, &quot;xxx&quot;: &quot;yyy&quot;&#125;</span></span><br><span class="line">            appendParameters(map, method, method.getName());</span><br><span class="line"></span><br><span class="line">            <span class="type">String</span> <span class="variable">retryKey</span> <span class="operator">=</span> method.getName() + <span class="string">&quot;.retry&quot;</span>;</span><br><span class="line">            <span class="keyword">if</span> (map.containsKey(retryKey)) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">retryValue</span> <span class="operator">=</span> map.remove(retryKey);</span><br><span class="line">                <span class="comment">// 检测 MethodConfig retry 是否为 false，若是，则设置重试次数为0</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="string">&quot;false&quot;</span>.equals(retryValue)) &#123;</span><br><span class="line">                    map.put(method.getName() + <span class="string">&quot;.retries&quot;</span>, <span class="string">&quot;0&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 获取 ArgumentConfig 列表</span></span><br><span class="line">            List&lt;ArgumentConfig&gt; arguments = method.getArguments();</span><br><span class="line">            <span class="keyword">if</span> (arguments != <span class="literal">null</span> &amp;&amp; !arguments.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">for</span> (ArgumentConfig argument : arguments) &#123;</span><br><span class="line">                    <span class="comment">// 检测 type 属性是否为空，或者空串（分支1 ⭐️）</span></span><br><span class="line">                    <span class="keyword">if</span> (argument.getType() != <span class="literal">null</span> &amp;&amp; argument.getType().length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                        Method[] methods = interfaceClass.getMethods();</span><br><span class="line">                        <span class="keyword">if</span> (methods != <span class="literal">null</span> &amp;&amp; methods.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; methods.length; i++) &#123;</span><br><span class="line">                                <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> methods[i].getName();</span><br><span class="line">                                <span class="comment">// 比对方法名，查找目标方法</span></span><br><span class="line">                                <span class="keyword">if</span> (methodName.equals(method.getName())) &#123;</span><br><span class="line">                                    Class&lt;?&gt;[] argtypes = methods[i].getParameterTypes();</span><br><span class="line">                                    <span class="keyword">if</span> (argument.getIndex() != -<span class="number">1</span>) &#123;</span><br><span class="line">                                        <span class="comment">// 检测 ArgumentConfig 中的 type 属性与方法参数列表</span></span><br><span class="line">                                        <span class="comment">// 中的参数名称是否一致，不一致则抛出异常(分支2 ⭐️)</span></span><br><span class="line">                                        <span class="keyword">if</span> (argtypes[argument.getIndex()].getName().equals(argument.getType())) &#123;</span><br><span class="line">                                            <span class="comment">// 添加 ArgumentConfig 字段信息到 map 中，</span></span><br><span class="line">                                            <span class="comment">// 键前缀 = 方法名.index，比如:</span></span><br><span class="line">                                            <span class="comment">// map = &#123;&quot;sayHello.3&quot;: true&#125;</span></span><br><span class="line">                                            appendParameters(map, argument, method.getName() + <span class="string">&quot;.&quot;</span> + argument.getIndex());</span><br><span class="line">                                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;argument config error: ...&quot;</span>);</span><br><span class="line">                                        &#125;</span><br><span class="line">                                    &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 分支3 ⭐️</span></span><br><span class="line">                                        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; argtypes.length; j++) &#123;</span><br><span class="line">                                            Class&lt;?&gt; argclazz = argtypes[j];</span><br><span class="line">                                            <span class="comment">// 从参数类型列表中查找类型名称为 argument.type 的参数</span></span><br><span class="line">                                            <span class="keyword">if</span> (argclazz.getName().equals(argument.getType())) &#123;</span><br><span class="line">                                                appendParameters(map, argument, method.getName() + <span class="string">&quot;.&quot;</span> + j);</span><br><span class="line">                                                <span class="keyword">if</span> (argument.getIndex() != -<span class="number">1</span> &amp;&amp; argument.getIndex() != j) &#123;</span><br><span class="line">                                                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;argument config error: ...&quot;</span>);</span><br><span class="line">                                                &#125;</span><br><span class="line">                                            &#125;</span><br><span class="line">                                        &#125;</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 用户未配置 type 属性，但配置了 index 属性，且 index != -1</span></span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (argument.getIndex() != -<span class="number">1</span>) &#123;    <span class="comment">// 分支4 ⭐️</span></span><br><span class="line">                        <span class="comment">// 添加 ArgumentConfig 字段信息到 map 中</span></span><br><span class="line">                        appendParameters(map, argument, method.getName() + <span class="string">&quot;.&quot;</span> + argument.getIndex());</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;argument config must set index or type&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面这段代码 for 循环和 if else 分支嵌套太多，导致层次太深，不利于阅读，需要耐心看一下。大家在看这段代码时，注意把几个重要的条件分支找出来。只要理解了这几个分支的意图，就可以弄懂这段代码。我在上面代码中用⭐️符号标识出了4个重要的分支，下面用伪代码解释一下这几个分支的含义。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取 ArgumentConfig 列表</span></span><br><span class="line"><span class="keyword">for</span> (遍历 ArgumentConfig 列表) &#123;</span><br><span class="line">    <span class="keyword">if</span> (type 不为 <span class="literal">null</span>，也不为空串) &#123;    <span class="comment">// 分支1</span></span><br><span class="line">        <span class="number">1.</span> 通过反射获取 interfaceClass 的方法列表</span><br><span class="line">        <span class="keyword">for</span> (遍历方法列表) &#123;</span><br><span class="line">            <span class="number">1.</span> 比对方法名，查找目标方法</span><br><span class="line">            <span class="number">2.</span> 通过反射获取目标方法的参数类型数组 argtypes</span><br><span class="line">            <span class="title function_">if</span> <span class="params">(index != -<span class="number">1</span>)</span> &#123;    <span class="comment">// 分支2</span></span><br><span class="line">                <span class="number">1.</span> 从 argtypes 数组中获取下标 index 处的元素 argType</span><br><span class="line">                <span class="number">2.</span> 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致</span><br><span class="line">                <span class="number">3.</span> 添加 ArgumentConfig 字段信息到 map 中，或抛出异常</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 分支3</span></span><br><span class="line">                <span class="number">1.</span> 遍历参数类型数组 argtypes，查找 argument.type 类型的参数</span><br><span class="line">                <span class="number">2.</span> 添加 ArgumentConfig 字段信息到 map 中</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (index != -<span class="number">1</span>) &#123;    <span class="comment">// 分支4</span></span><br><span class="line">        <span class="number">1.</span> 添加 ArgumentConfig 字段信息到 map 中</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在本节分析的源码中，appendParameters 这个方法出现的次数比较多，该方法用于将对象字段信息添加到 map 中。实现上则是通过反射获取目标对象的 getter 方法，并调用该方法获取属性值。然后再通过 getter 方法名解析出属性名，比如从方法名 getName 中可解析出属性 name。如果用户传入了属性名前缀，此时需要将属性名加入前缀内容。最后将 &lt;属性名，属性值&gt; 键值对存入到 map 中就行了。限于篇幅原因，这里就不分析 appendParameters 方法的源码了，大家请自行分析。</p><h3 id="2-2-导出-Dubbo-服务"><a href="#2-2-导出-Dubbo-服务" class="headerlink" title="2.2 导出 Dubbo 服务"></a>2.2 导出 Dubbo 服务</h3><p>前置工作做完，接下来就可以进行服务导出工作。服务导出，分为导出到本地 (JVM)，和导出到远程。在深入分析服务导出源码前，我们先来从宏观层面上看一下服务导出逻辑。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doExportUrlsFor1Protocol</span><span class="params">(ProtocolConfig protocolConfig, List&lt;URL&gt; registryURLs)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关代码</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)</span><br><span class="line">            .hasExtension(url.getProtocol())) &#123;</span><br><span class="line">        <span class="comment">// 加载 ConfiguratorFactory，并生成 Configurator 配置 url</span></span><br><span class="line">        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)</span><br><span class="line">                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">scope</span> <span class="operator">=</span> url.getParameter(Constants.SCOPE_KEY);</span><br><span class="line">    <span class="comment">// 如果 scope = none，则什么都不做</span></span><br><span class="line">    <span class="keyword">if</span> (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) &#123;</span><br><span class="line">        <span class="comment">// scope != remote，导出到本地</span></span><br><span class="line">        <span class="keyword">if</span> (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) &#123;</span><br><span class="line">            exportLocal(url);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// scope != local，导出到远程</span></span><br><span class="line">        <span class="keyword">if</span> (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (registryURLs != <span class="literal">null</span> &amp;&amp; !registryURLs.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">for</span> (URL registryURL : registryURLs) &#123;</span><br><span class="line">                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));</span><br><span class="line">                    <span class="comment">// 加载监视器链接</span></span><br><span class="line">                    <span class="type">URL</span> <span class="variable">monitorUrl</span> <span class="operator">=</span> loadMonitor(registryURL);</span><br><span class="line">                    <span class="keyword">if</span> (monitorUrl != <span class="literal">null</span>) &#123;</span><br><span class="line">                        <span class="comment">// 将监视器链接作为参数添加到 url 中</span></span><br><span class="line">                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="type">String</span> <span class="variable">proxy</span> <span class="operator">=</span> url.getParameter(Constants.PROXY_KEY);</span><br><span class="line">                    <span class="keyword">if</span> (StringUtils.isNotEmpty(proxy)) &#123;</span><br><span class="line">                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 为服务提供类(ref)生成 Invoker</span></span><br><span class="line">                    Invoker&lt;?&gt; invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));</span><br><span class="line">                    <span class="comment">// DelegateProviderMetaDataInvoker 仅用于持有 Invoker 和 ServiceConfig</span></span><br><span class="line">                    <span class="type">DelegateProviderMetaDataInvoker</span> <span class="variable">wrapperInvoker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DelegateProviderMetaDataInvoker</span>(invoker, <span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 导出服务，并生成 Exporter</span></span><br><span class="line">                    Exporter&lt;?&gt; exporter = protocol.export(wrapperInvoker);</span><br><span class="line">                    exporters.add(exporter);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 不存在注册中心，仅导出服务</span></span><br><span class="line">                Invoker&lt;?&gt; invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);</span><br><span class="line">                <span class="type">DelegateProviderMetaDataInvoker</span> <span class="variable">wrapperInvoker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DelegateProviderMetaDataInvoker</span>(invoker, <span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">                Exporter&lt;?&gt; exporter = protocol.export(wrapperInvoker);</span><br><span class="line">                exporters.add(exporter);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.urls.add(url);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码根据 url 中的 scope 参数决定服务导出方式，分别如下：</p><ul><li>scope &#x3D; none，不导出服务</li><li>scope !&#x3D; remote，导出到本地</li><li>scope !&#x3D; local，导出到远程</li></ul><p>不管是导出到本地，还是远程。进行服务导出之前，均需要先创建 Invoker。这是一个很重要的步骤，因此接下来我会先分析 Invoker 的创建过程。</p><h3 id="2-2-1-Invoker-创建过程"><a href="#2-2-1-Invoker-创建过程" class="headerlink" title="2.2.1 Invoker 创建过程"></a>2.2.1 Invoker 创建过程</h3><p>在 Dubbo 中，Invoker 是一个非常重要的模型。在服务提供端，以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明，这里引用一下。</p><blockquote><p>Invoker 是实体域，它是 Dubbo 的核心模型，其它模型都向它靠扰，或转换成它，它代表一个可执行体，可向它发起 invoke 调用，它有可能是一个本地的实现，也可能是一个远程的实现，也可能一个集群实现。</p></blockquote><p>既然 Invoker 如此重要，那么我们很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 创建而来，Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中，探索 Invoker 的创建过程。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">-- JavassistProxyFactory</span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">getInvoker</span><span class="params">(T proxy, Class&lt;T&gt; type, URL url)</span> &#123;</span><br><span class="line">    <span class="comment">// 为目标类创建 Wrapper</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">Wrapper</span> <span class="variable">wrapper</span> <span class="operator">=</span> Wrapper.getWrapper(proxy.getClass().getName().indexOf(<span class="string">&#x27;$&#x27;</span>) &lt; <span class="number">0</span> ? proxy.getClass() : type);</span><br><span class="line">    <span class="comment">// 创建匿名 Invoker 类对象，并实现 doInvoke 方法。</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AbstractProxyInvoker</span>&lt;T&gt;(proxy, type, url) &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> Object <span class="title function_">doInvoke</span><span class="params">(T proxy, String methodName,</span></span><br><span class="line"><span class="params">                                  Class&lt;?&gt;[] parameterTypes,</span></span><br><span class="line"><span class="params">                                  Object[] arguments)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">            <span class="comment">// 调用 Wrapper 的 invokeMethod 方法，invokeMethod 最终会调用目标方法</span></span><br><span class="line">            <span class="keyword">return</span> wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象，并覆写了抽象方法 doInvoke。覆写后的 doInvoke 逻辑比较简单，仅是将调用请求转发给了 Wrapper 类的 invokeMethod 方法。Wrapper 用于“包裹”目标类，Wrapper 是一个抽象类，仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中，子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析，拿到诸如类方法，类成员变量等信息。以及生成 invokeMethod 方法代码，和其他一些方法代码。代码生成完毕后，通过 Javassist 生成 Class 对象，最后再通过反射创建 Wrapper 实例。相关的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> Wrapper <span class="title function_">getWrapper</span><span class="params">(Class&lt;?&gt; c)</span> &#123;</span><br><span class="line">    <span class="keyword">while</span> (ClassGenerator.isDynamicClass(c))</span><br><span class="line">        c = c.getSuperclass();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (c == Object.class)</span><br><span class="line">        <span class="keyword">return</span> OBJECT_WRAPPER;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 访存</span></span><br><span class="line">    <span class="type">Wrapper</span> <span class="variable">ret</span> <span class="operator">=</span> WRAPPER_MAP.get(c);</span><br><span class="line">    <span class="keyword">if</span> (ret == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 缓存未命中，创建 Wrapper</span></span><br><span class="line">        ret = makeWrapper(c);</span><br><span class="line">        <span class="comment">// 写入缓存</span></span><br><span class="line">        WRAPPER_MAP.put(c, ret);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>getWrapper 方法只是包含了一些缓存操作逻辑，非重点。下面我们重点关注 makeWrapper 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Wrapper <span class="title function_">makeWrapper</span><span class="params">(Class&lt;?&gt; c)</span> &#123;</span><br><span class="line">    <span class="comment">// 检测 c 是否为私有类型，若是则抛出异常</span></span><br><span class="line">    <span class="keyword">if</span> (c.isPrimitive())</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Can not create wrapper for primitive type: &quot;</span> + c);</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> c.getName();</span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> ClassHelper.getClassLoader(c);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// c1 用于存储 setPropertyValue 方法代码</span></span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">c1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="string">&quot;public void setPropertyValue(Object o, String n, Object v)&#123; &quot;</span>);</span><br><span class="line">    <span class="comment">// c2 用于存储 getPropertyValue 方法代码</span></span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">c2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="string">&quot;public Object getPropertyValue(Object o, String n)&#123; &quot;</span>);</span><br><span class="line">    <span class="comment">// c3 用于存储 invokeMethod 方法代码</span></span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">c3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="string">&quot;public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws &quot;</span> + InvocationTargetException.class.getName() + <span class="string">&quot;&#123; &quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生成类型转换代码及异常捕捉代码，比如：</span></span><br><span class="line">    <span class="comment">//   DemoService w; try &#123; w = ((DemoServcie) $1); &#125;&#125;catch(Throwable e)&#123; throw new IllegalArgumentException(e); &#125;</span></span><br><span class="line">    c1.append(name).append(<span class="string">&quot; w; try&#123; w = ((&quot;</span>).append(name).append(<span class="string">&quot;)$1); &#125;catch(Throwable e)&#123; throw new IllegalArgumentException(e); &#125;&quot;</span>);</span><br><span class="line">    c2.append(name).append(<span class="string">&quot; w; try&#123; w = ((&quot;</span>).append(name).append(<span class="string">&quot;)$1); &#125;catch(Throwable e)&#123; throw new IllegalArgumentException(e); &#125;&quot;</span>);</span><br><span class="line">    c3.append(name).append(<span class="string">&quot; w; try&#123; w = ((&quot;</span>).append(name).append(<span class="string">&quot;)$1); &#125;catch(Throwable e)&#123; throw new IllegalArgumentException(e); &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// pts 用于存储成员变量名和类型</span></span><br><span class="line">    Map&lt;String, Class&lt;?&gt;&gt; pts = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, Class&lt;?&gt;&gt;();</span><br><span class="line">    <span class="comment">// ms 用于存储方法描述信息（可理解为方法签名）及 Method 实例</span></span><br><span class="line">    Map&lt;String, Method&gt; ms = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span>&lt;String, Method&gt;();</span><br><span class="line">    <span class="comment">// mns 为方法名列表</span></span><br><span class="line">    List&lt;String&gt; mns = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;String&gt;();</span><br><span class="line">    <span class="comment">// dmns 用于存储定义在当前类中的方法的名称</span></span><br><span class="line">    List&lt;String&gt; dmns = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;String&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --------------------------------✨ 分割线1 ✨-------------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 public 访问级别的字段，并为所有字段生成条件判断语句</span></span><br><span class="line">    <span class="keyword">for</span> (Field f : c.getFields()) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">fn</span> <span class="operator">=</span> f.getName();</span><br><span class="line">        Class&lt;?&gt; ft = f.getType();</span><br><span class="line">        <span class="keyword">if</span> (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))</span><br><span class="line">            <span class="comment">// 忽略关键字 static 或 transient 修饰的变量</span></span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成条件判断及赋值语句，比如：</span></span><br><span class="line">        <span class="comment">// if( $2.equals(&quot;name&quot;) ) &#123; w.name = (java.lang.String) $3; return;&#125;</span></span><br><span class="line">        <span class="comment">// if( $2.equals(&quot;age&quot;) ) &#123; w.age = ((Number) $3).intValue(); return;&#125;</span></span><br><span class="line">        c1.append(<span class="string">&quot; if( $2.equals(\&quot;&quot;</span>).append(fn).append(<span class="string">&quot;\&quot;) )&#123; w.&quot;</span>).append(fn).append(<span class="string">&quot;=&quot;</span>).append(arg(ft, <span class="string">&quot;$3&quot;</span>)).append(<span class="string">&quot;; return; &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成条件判断及返回语句，比如：</span></span><br><span class="line">        <span class="comment">// if( $2.equals(&quot;name&quot;) ) &#123; return ($w)w.name; &#125;</span></span><br><span class="line">        c2.append(<span class="string">&quot; if( $2.equals(\&quot;&quot;</span>).append(fn).append(<span class="string">&quot;\&quot;) )&#123; return ($w)w.&quot;</span>).append(fn).append(<span class="string">&quot;; &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 存储 &lt;字段名, 字段类型&gt; 键值对到 pts 中</span></span><br><span class="line">        pts.put(fn, ft);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --------------------------------✨ 分割线2 ✨-------------------------------------</span></span><br><span class="line"></span><br><span class="line">    Method[] methods = c.getMethods();</span><br><span class="line">    <span class="comment">// 检测 c 中是否包含在当前类中声明的方法</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">hasMethod</span> <span class="operator">=</span> hasMethods(methods);</span><br><span class="line">    <span class="keyword">if</span> (hasMethod) &#123;</span><br><span class="line">        c3.append(<span class="string">&quot; try&#123;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (Method m : methods) &#123;</span><br><span class="line">        <span class="keyword">if</span> (m.getDeclaringClass() == Object.class)</span><br><span class="line">            <span class="comment">// 忽略 Object 中定义的方法</span></span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">mn</span> <span class="operator">=</span> m.getName();</span><br><span class="line">        <span class="comment">// 生成方法名判断语句，示例如下：</span></span><br><span class="line">        <span class="comment">// if ( &quot;sayHello&quot;.equals( $2 )</span></span><br><span class="line">        c3.append(<span class="string">&quot; if( \&quot;&quot;</span>).append(mn).append(<span class="string">&quot;\&quot;.equals( $2 ) &quot;</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> m.getParameterTypes().length;</span><br><span class="line">        <span class="comment">// 生成运行时传入参数的数量与方法的参数列表长度判断语句，示例如下：</span></span><br><span class="line">        <span class="comment">// &amp;&amp; $3.length == 2</span></span><br><span class="line">        c3.append(<span class="string">&quot; &amp;&amp; &quot;</span>).append(<span class="string">&quot; $3.length == &quot;</span>).append(len);</span><br><span class="line"></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">override</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (Method m2 : methods) &#123;</span><br><span class="line">            <span class="comment">// 检测方法是否存在重载情况，条件为：方法对象不同 &amp;&amp; 方法名相同</span></span><br><span class="line">            <span class="keyword">if</span> (m != m2 &amp;&amp; m.getName().equals(m2.getName())) &#123;</span><br><span class="line">                override = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 对重载方法进行处理，考虑下面的方法：</span></span><br><span class="line">        <span class="comment">//    1. void sayHello(Integer, String)</span></span><br><span class="line">        <span class="comment">//    2. void sayHello(Integer, Integer)</span></span><br><span class="line">        <span class="comment">// 方法名相同，参数列表长度也相同，因此不能仅通过这两项判断两个方法是否相等。</span></span><br><span class="line">        <span class="comment">// 需要进一步判断方法的参数类型</span></span><br><span class="line">        <span class="keyword">if</span> (override) &#123;</span><br><span class="line">            <span class="keyword">if</span> (len &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">l</span> <span class="operator">=</span> <span class="number">0</span>; l &lt; len; l++) &#123;</span><br><span class="line">                    <span class="comment">// &amp;&amp; $3[0].getName().equals(&quot;java.lang.Integer&quot;) </span></span><br><span class="line">                    <span class="comment">//    &amp;&amp; $3[1].getName().equals(&quot;java.lang.String&quot;)</span></span><br><span class="line">                    c3.append(<span class="string">&quot; &amp;&amp; &quot;</span>).append(<span class="string">&quot; $3[&quot;</span>).append(l).append(<span class="string">&quot;].getName().equals(\&quot;&quot;</span>)</span><br><span class="line">                            .append(m.getParameterTypes()[l].getName()).append(<span class="string">&quot;\&quot;)&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加 ) &#123;，完成方法判断语句，此时生成的方法可能如下（已格式化）：</span></span><br><span class="line">        <span class="comment">// if (&quot;sayHello&quot;.equals($2) </span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3.length == 2</span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3[0].getName().equals(&quot;java.lang.Integer&quot;) </span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3[1].getName().equals(&quot;java.lang.String&quot;)) &#123;</span></span><br><span class="line">        c3.append(<span class="string">&quot; ) &#123; &quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 根据返回值类型生成目标方法调用语句</span></span><br><span class="line">        <span class="keyword">if</span> (m.getReturnType() == Void.TYPE)</span><br><span class="line">            <span class="comment">// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;</span></span><br><span class="line">            c3.append(<span class="string">&quot; w.&quot;</span>).append(mn).append(<span class="string">&#x27;(&#x27;</span>).append(args(m.getParameterTypes(), <span class="string">&quot;$4&quot;</span>)).append(<span class="string">&quot;);&quot;</span>).append(<span class="string">&quot; return null;&quot;</span>);</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="comment">// return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);</span></span><br><span class="line">            c3.append(<span class="string">&quot; return ($w)w.&quot;</span>).append(mn).append(<span class="string">&#x27;(&#x27;</span>).append(args(m.getParameterTypes(), <span class="string">&quot;$4&quot;</span>)).append(<span class="string">&quot;);&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加 &#125;, 当前”方法判断条件“代码生成完毕，示例代码如下（已格式化）：</span></span><br><span class="line">        <span class="comment">// if (&quot;sayHello&quot;.equals($2) </span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3.length == 2</span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3[0].getName().equals(&quot;java.lang.Integer&quot;) </span></span><br><span class="line">        <span class="comment">//     &amp;&amp; $3[1].getName().equals(&quot;java.lang.String&quot;)) &#123;</span></span><br><span class="line">        <span class="comment">//</span></span><br><span class="line">        <span class="comment">//     w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); </span></span><br><span class="line">        <span class="comment">//     return null;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        c3.append(<span class="string">&quot; &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 添加方法名到 mns 集合中</span></span><br><span class="line">        mns.add(mn);</span><br><span class="line">        <span class="comment">// 检测当前方法是否在 c 中被声明的</span></span><br><span class="line">        <span class="keyword">if</span> (m.getDeclaringClass() == c)</span><br><span class="line">            <span class="comment">// 若是，则将当前方法名添加到 dmns 中</span></span><br><span class="line">            dmns.add(mn);</span><br><span class="line">        ms.put(ReflectUtils.getDesc(m), m);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (hasMethod) &#123;</span><br><span class="line">        <span class="comment">// 添加异常捕捉语句</span></span><br><span class="line">        c3.append(<span class="string">&quot; &#125; catch(Throwable e) &#123; &quot;</span>);</span><br><span class="line">        c3.append(<span class="string">&quot;     throw new java.lang.reflect.InvocationTargetException(e); &quot;</span>);</span><br><span class="line">        c3.append(<span class="string">&quot; &#125;&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加 NoSuchMethodException 异常抛出代码</span></span><br><span class="line">    c3.append(<span class="string">&quot; throw new &quot;</span> + NoSuchMethodException.class.getName() + <span class="string">&quot;(\&quot;Not found method \\\&quot;\&quot;+$2+\&quot;\\\&quot; in class &quot;</span> + c.getName() + <span class="string">&quot;.\&quot;); &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --------------------------------✨ 分割线3 ✨-------------------------------------</span></span><br><span class="line"></span><br><span class="line">    Matcher matcher;</span><br><span class="line">    <span class="comment">// 处理 get/set 方法</span></span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;String, Method&gt; entry : ms.entrySet()) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">md</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> (Method) entry.getValue();</span><br><span class="line">        <span class="comment">// 匹配以 get 开头的方法</span></span><br><span class="line">        <span class="keyword">if</span> ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) &#123;</span><br><span class="line">            <span class="comment">// 获取属性名</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">pn</span> <span class="operator">=</span> propertyName(matcher.group(<span class="number">1</span>));</span><br><span class="line">            <span class="comment">// 生成属性判断以及返回语句，示例如下：</span></span><br><span class="line">            <span class="comment">// if( $2.equals(&quot;name&quot;) ) &#123; return ($w).w.getName(); &#125;</span></span><br><span class="line">            c2.append(<span class="string">&quot; if( $2.equals(\&quot;&quot;</span>).append(pn).append(<span class="string">&quot;\&quot;) )&#123; return ($w)w.&quot;</span>).append(method.getName()).append(<span class="string">&quot;(); &#125;&quot;</span>);</span><br><span class="line">            pts.put(pn, method.getReturnType());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 匹配以 is/has/can 开头的方法</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">pn</span> <span class="operator">=</span> propertyName(matcher.group(<span class="number">1</span>));</span><br><span class="line">            <span class="comment">// 生成属性判断以及返回语句，示例如下：</span></span><br><span class="line">            <span class="comment">// if( $2.equals(&quot;dream&quot;) ) &#123; return ($w).w.hasDream(); &#125;</span></span><br><span class="line">            c2.append(<span class="string">&quot; if( $2.equals(\&quot;&quot;</span>).append(pn).append(<span class="string">&quot;\&quot;) )&#123; return ($w)w.&quot;</span>).append(method.getName()).append(<span class="string">&quot;(); &#125;&quot;</span>);</span><br><span class="line">            pts.put(pn, method.getReturnType());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 匹配以 set 开头的方法</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) &#123;</span><br><span class="line">            Class&lt;?&gt; pt = method.getParameterTypes()[<span class="number">0</span>];</span><br><span class="line">            <span class="type">String</span> <span class="variable">pn</span> <span class="operator">=</span> propertyName(matcher.group(<span class="number">1</span>));</span><br><span class="line">            <span class="comment">// 生成属性判断以及 setter 调用语句，示例如下：</span></span><br><span class="line">            <span class="comment">// if( $2.equals(&quot;name&quot;) ) &#123; w.setName((java.lang.String)$3); return; &#125;</span></span><br><span class="line">            c1.append(<span class="string">&quot; if( $2.equals(\&quot;&quot;</span>).append(pn).append(<span class="string">&quot;\&quot;) )&#123; w.&quot;</span>).append(method.getName()).append(<span class="string">&quot;(&quot;</span>).append(arg(pt, <span class="string">&quot;$3&quot;</span>)).append(<span class="string">&quot;); return; &#125;&quot;</span>);</span><br><span class="line">            pts.put(pn, pt);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加 NoSuchPropertyException 异常抛出代码</span></span><br><span class="line">    c1.append(<span class="string">&quot; throw new &quot;</span> + NoSuchPropertyException.class.getName() + <span class="string">&quot;(\&quot;Not found property \\\&quot;\&quot;+$2+\&quot;\\\&quot; filed or setter method in class &quot;</span> + c.getName() + <span class="string">&quot;.\&quot;); &#125;&quot;</span>);</span><br><span class="line">    c2.append(<span class="string">&quot; throw new &quot;</span> + NoSuchPropertyException.class.getName() + <span class="string">&quot;(\&quot;Not found property \\\&quot;\&quot;+$2+\&quot;\\\&quot; filed or setter method in class &quot;</span> + c.getName() + <span class="string">&quot;.\&quot;); &#125;&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// --------------------------------✨ 分割线4 ✨-------------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">id</span> <span class="operator">=</span> WRAPPER_CLASS_COUNTER.getAndIncrement();</span><br><span class="line">    <span class="comment">// 创建类生成器</span></span><br><span class="line">    <span class="type">ClassGenerator</span> <span class="variable">cc</span> <span class="operator">=</span> ClassGenerator.newInstance(cl);</span><br><span class="line">    <span class="comment">// 设置类名及超类</span></span><br><span class="line">    cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + <span class="string">&quot;$sw&quot;</span>) + id);</span><br><span class="line">    cc.setSuperClass(Wrapper.class);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加默认构造方法</span></span><br><span class="line">    cc.addDefaultConstructor();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加字段</span></span><br><span class="line">    cc.addField(<span class="string">&quot;public static String[] pns;&quot;</span>);</span><br><span class="line">    cc.addField(<span class="string">&quot;public static &quot;</span> + Map.class.getName() + <span class="string">&quot; pts;&quot;</span>);</span><br><span class="line">    cc.addField(<span class="string">&quot;public static String[] mns;&quot;</span>);</span><br><span class="line">    cc.addField(<span class="string">&quot;public static String[] dmns;&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>, len = ms.size(); i &lt; len; i++)</span><br><span class="line">        cc.addField(<span class="string">&quot;public static Class[] mts&quot;</span> + i + <span class="string">&quot;;&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加方法代码</span></span><br><span class="line">    cc.addMethod(<span class="string">&quot;public String[] getPropertyNames()&#123; return pns; &#125;&quot;</span>);</span><br><span class="line">    cc.addMethod(<span class="string">&quot;public boolean hasProperty(String n)&#123; return pts.containsKey($1); &#125;&quot;</span>);</span><br><span class="line">    cc.addMethod(<span class="string">&quot;public Class getPropertyType(String n)&#123; return (Class)pts.get($1); &#125;&quot;</span>);</span><br><span class="line">    cc.addMethod(<span class="string">&quot;public String[] getMethodNames()&#123; return mns; &#125;&quot;</span>);</span><br><span class="line">    cc.addMethod(<span class="string">&quot;public String[] getDeclaredMethodNames()&#123; return dmns; &#125;&quot;</span>);</span><br><span class="line">    cc.addMethod(c1.toString());</span><br><span class="line">    cc.addMethod(c2.toString());</span><br><span class="line">    cc.addMethod(c3.toString());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 生成类</span></span><br><span class="line">        Class&lt;?&gt; wc = cc.toClass();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置字段值</span></span><br><span class="line">        wc.getField(<span class="string">&quot;pts&quot;</span>).set(<span class="literal">null</span>, pts);</span><br><span class="line">        wc.getField(<span class="string">&quot;pns&quot;</span>).set(<span class="literal">null</span>, pts.keySet().toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">        wc.getField(<span class="string">&quot;mns&quot;</span>).set(<span class="literal">null</span>, mns.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">        wc.getField(<span class="string">&quot;dmns&quot;</span>).set(<span class="literal">null</span>, dmns.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]));</span><br><span class="line">        <span class="type">int</span> <span class="variable">ix</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (Method m : ms.values())</span><br><span class="line">            wc.getField(<span class="string">&quot;mts&quot;</span> + ix++).set(<span class="literal">null</span>, m.getParameterTypes());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建 Wrapper 实例</span></span><br><span class="line">        <span class="keyword">return</span> (Wrapper) wc.newInstance();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e.getMessage(), e);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        cc.release();</span><br><span class="line">        ms.clear();</span><br><span class="line">        mns.clear();</span><br><span class="line">        dmns.clear();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码很长，大家耐心看一下。我在上面代码中做了大量的注释，并按功能对代码进行了分块，以帮助大家理解代码逻辑。下面对这段代码进行讲解。首先我们把目光移到分割线1之上的代码，这段代码主要用于进行一些初始化操作。比如创建 c1、c2、c3 以及 pts、ms、mns 等变量，以及向 c1、c2、c3 中添加方法定义和类型类型转换代码。接下来是分割线1到分割线2之间的代码，这段代码用于为 public 级别的字段生成条件判断取值与赋值代码。这段代码不是很难看懂，就不多说了。继续向下看，分割线2和分隔线3之间的代码用于为定义在当前类中的方法生成判断语句，和方法调用语句。因为需要对方法重载进行校验，因此到这这段代码看起来有点复杂。不过耐心开一下，也不是很难理解。接下来是分割线3和分隔线4之间的代码，这段代码用于处理 getter、setter 以及以 is&#x2F;has&#x2F;can 开头的方法。处理方式是通过正则表达式获取方法类型（get&#x2F;set&#x2F;is&#x2F;…），以及属性名。之后为属性名生成判断语句，然后为方法生成调用语句。最后我们再来看一下分隔线4以下的代码，这段代码通过 ClassGenerator 为刚刚生成的代码构建 Class 类，并通过反射创建对象。ClassGenerator 是 Dubbo 自己封装的，该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain)，该方法通过 javassist 构建 Class。这里就不分析 toClass 方法了，大家请自行分析。</p><p>阅读 Wrapper 类代码需要对 javassist 框架有所了解。关于 javassist，大家如果不熟悉，请自行查阅资料，本节不打算介绍 javassist 相关内容。</p><p>好了，关于 Wrapper 类生成过程就分析到这。如果大家看的不是很明白，可以单独为 Wrapper 创建单元测试，然后单步调试。并将生成的代码拷贝出来，格式化后再进行观察和理解。好了，本节先到这。</p><h3 id="2-2-2-导出服务到本地"><a href="#2-2-2-导出服务到本地" class="headerlink" title="2.2.2 导出服务到本地"></a>2.2.2 导出服务到本地</h3><p>本节我们来看一下服务导出相关的代码，按照代码执行顺序，本节先来分析导出服务到本地的过程。相关代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">exportLocal</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="comment">// 如果 URL 的协议头等于 injvm，说明已经导出到本地了，无需再次导出</span></span><br><span class="line">    <span class="keyword">if</span> (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) &#123;</span><br><span class="line">        <span class="type">URL</span> <span class="variable">local</span> <span class="operator">=</span> URL.valueOf(url.toFullString())</span><br><span class="line">            .setProtocol(Constants.LOCAL_PROTOCOL)    <span class="comment">// 设置协议头为 injvm</span></span><br><span class="line">            .setHost(LOCALHOST)</span><br><span class="line">            .setPort(<span class="number">0</span>);</span><br><span class="line">        ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));</span><br><span class="line">        <span class="comment">// 创建 Invoker，并导出服务，这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法</span></span><br><span class="line">        Exporter&lt;?&gt; exporter = protocol.export(</span><br><span class="line">            proxyFactory.getInvoker(ref, (Class) interfaceClass, local));</span><br><span class="line">        exporters.add(exporter);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>exportLocal 方法比较简单，首先根据 URL 协议头决定是否导出服务。若需导出，则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker，并调用 InjvmProtocol 的 export 方法导出服务。下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Exporter&lt;T&gt; <span class="title function_">export</span><span class="params">(Invoker&lt;T&gt; invoker)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="comment">// 创建 InjvmExporter</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">InjvmExporter</span>&lt;T&gt;(invoker, invoker.getUrl().getServiceKey(), exporterMap);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter，无其他逻辑。到此导出服务到本地就分析完了，接下来，我们继续分析导出服务到远程的过程。</p><h3 id="2-2-3-导出服务到远程"><a href="#2-2-3-导出服务到远程" class="headerlink" title="2.2.3 导出服务到远程"></a>2.2.3 导出服务到远程</h3><p>与导出服务到本地相比，导出服务到远程的过程要复杂不少，其包含了服务导出与服务注册两个过程。这两个过程涉及到了大量的调用，因此比较复杂。不过不管再难，我们都要看一下，万一看懂了呢。按照代码执行顺序，本节先来分析服务导出逻辑，服务注册逻辑将在下一节进行分析。下面开始分析，我们把目光移动到 RegistryProtocol 的 export 方法上。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Exporter&lt;T&gt; <span class="title function_">export</span><span class="params">(<span class="keyword">final</span> Invoker&lt;T&gt; originInvoker)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="comment">// 导出服务</span></span><br><span class="line">    <span class="keyword">final</span> ExporterChangeableWrapper&lt;T&gt; exporter = doLocalExport(originInvoker);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取注册中心 URL，以 zookeeper 注册中心为例，得到的示例 URL 如下：</span></span><br><span class="line">    <span class="comment">// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&amp;dubbo=2.0.2&amp;export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider</span></span><br><span class="line">    <span class="type">URL</span> <span class="variable">registryUrl</span> <span class="operator">=</span> getRegistryUrl(originInvoker);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据 URL 加载 Registry 实现类，比如 ZookeeperRegistry</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">Registry</span> <span class="variable">registry</span> <span class="operator">=</span> getRegistry(originInvoker);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取已注册的服务提供者 URL，比如：</span></span><br><span class="line">    <span class="comment">// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">URL</span> <span class="variable">registeredProviderUrl</span> <span class="operator">=</span> getRegisteredProviderUrl(originInvoker);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 register 参数</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">register</span> <span class="operator">=</span> registeredProviderUrl.getParameter(<span class="string">&quot;register&quot;</span>, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 向服务提供者与消费者注册表中注册服务提供者</span></span><br><span class="line">    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据 register 的值决定是否注册服务</span></span><br><span class="line">    <span class="keyword">if</span> (register) &#123;</span><br><span class="line">        <span class="comment">// 向注册中心注册服务</span></span><br><span class="line">        register(registryUrl, registeredProviderUrl);</span><br><span class="line">        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取订阅 URL，比如：</span></span><br><span class="line">    <span class="comment">// provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&amp;check=false&amp;anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">URL</span> <span class="variable">overrideSubscribeUrl</span> <span class="operator">=</span> getSubscribedOverrideUrl(registeredProviderUrl);</span><br><span class="line">    <span class="comment">// 创建监听器</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">OverrideListener</span> <span class="variable">overrideSubscribeListener</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OverrideListener</span>(overrideSubscribeUrl, originInvoker);</span><br><span class="line">    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);</span><br><span class="line">    <span class="comment">// 向注册中心进行订阅 override 数据</span></span><br><span class="line">    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);</span><br><span class="line">    <span class="comment">// 创建并返回 DestroyableExporter</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">DestroyableExporter</span>&lt;T&gt;(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码看起来比较复杂，主要做如下一些操作：</p><ol><li>调用 doLocalExport 导出服务</li><li>向注册中心注册服务</li><li>向注册中心进行订阅 override 数据</li><li>创建并返回 DestroyableExporter</li></ol><p>在以上操作中，除了创建并返回 DestroyableExporter 没啥难度外，其他几步操作都不是很简单。这其中，导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据这个是非重点内容，后面会简单介绍一下。下面开始本节的分析，先来分析 doLocalExport 方法的逻辑，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> &lt;T&gt; ExporterChangeableWrapper&lt;T&gt; <span class="title function_">doLocalExport</span><span class="params">(<span class="keyword">final</span> Invoker&lt;T&gt; originInvoker)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> getCacheKey(originInvoker);</span><br><span class="line">    <span class="comment">// 访问缓存</span></span><br><span class="line">    ExporterChangeableWrapper&lt;T&gt; exporter = (ExporterChangeableWrapper&lt;T&gt;) bounds.get(key);</span><br><span class="line">    <span class="keyword">if</span> (exporter == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (bounds) &#123;</span><br><span class="line">            exporter = (ExporterChangeableWrapper&lt;T&gt;) bounds.get(key);</span><br><span class="line">            <span class="keyword">if</span> (exporter == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 创建 Invoker 为委托类对象</span></span><br><span class="line">                <span class="keyword">final</span> Invoker&lt;?&gt; invokerDelegete = <span class="keyword">new</span> <span class="title class_">InvokerDelegete</span>&lt;T&gt;(originInvoker, getProviderUrl(originInvoker));</span><br><span class="line">                <span class="comment">// 调用 protocol 的 export 方法导出服务</span></span><br><span class="line">                exporter = <span class="keyword">new</span> <span class="title class_">ExporterChangeableWrapper</span>&lt;T&gt;((Exporter&lt;T&gt;) protocol.export(invokerDelegete), originInvoker);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 写缓存</span></span><br><span class="line">                bounds.put(key, exporter);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> exporter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码是典型的双重检查，这个大家应该都知道。接下来，我们把重点放在 Protocol 的 export 方法上。假设运行时协议为 dubbo，此处的 protocol 会在运行时加载 DubboProtocol，并调用 DubboProtocol 的 export 方法。我们目光转移到 DubboProtocol 的 export 方法上，相关分析如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Exporter&lt;T&gt; <span class="title function_">export</span><span class="params">(Invoker&lt;T&gt; invoker)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> invoker.getUrl();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务标识，理解成服务坐标也行。由服务组名，服务名，服务版本号以及端口组成。比如：</span></span><br><span class="line">    <span class="comment">// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> serviceKey(url);</span><br><span class="line">    <span class="comment">// 创建 DubboExporter</span></span><br><span class="line">    DubboExporter&lt;T&gt; exporter = <span class="keyword">new</span> <span class="title class_">DubboExporter</span>&lt;T&gt;(invoker, key, exporterMap);</span><br><span class="line">    <span class="comment">// 将 &lt;key, exporter&gt; 键值对放入缓存中</span></span><br><span class="line">    exporterMap.put(key, exporter);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以下代码应该和本地存根有关，代码不难看懂，但具体用途暂时不清楚，先忽略</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">isStubSupportEvent</span> <span class="operator">=</span> url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);</span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">isCallbackservice</span> <span class="operator">=</span> url.getParameter(Constants.IS_CALLBACK_SERVICE, <span class="literal">false</span>);</span><br><span class="line">    <span class="keyword">if</span> (isStubSupportEvent &amp;&amp; !isCallbackservice) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">stubServiceMethods</span> <span class="operator">=</span> url.getParameter(Constants.STUB_EVENT_METHODS_KEY);</span><br><span class="line">        <span class="keyword">if</span> (stubServiceMethods == <span class="literal">null</span> || stubServiceMethods.length() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 省略日志打印代码</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 启动服务器</span></span><br><span class="line">    openServer(url);</span><br><span class="line">    <span class="comment">// 优化序列化</span></span><br><span class="line">    optimizeSerialization(url);</span><br><span class="line">    <span class="keyword">return</span> exporter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，我们重点关注 DubboExporter 的创建以及 openServer 方法，其他逻辑看不懂也没关系，不影响理解服务导出过程。另外，DubboExporter 的代码比较简单，就不分析了。下面分析 openServer 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">openServer</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="comment">// 获取 host:port，并将其作为服务器实例的 key，用于标识当前的服务器实例</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> url.getAddress();</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">isServer</span> <span class="operator">=</span> url.getParameter(Constants.IS_SERVER_KEY, <span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">if</span> (isServer) &#123;</span><br><span class="line">        <span class="comment">// 访问缓存</span></span><br><span class="line">        <span class="type">ExchangeServer</span> <span class="variable">server</span> <span class="operator">=</span> serverMap.get(key);</span><br><span class="line">        <span class="keyword">if</span> (server == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 创建服务器实例</span></span><br><span class="line">            serverMap.put(key, createServer(url));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 服务器已创建，则根据 url 中的配置重置服务器</span></span><br><span class="line">            server.reset(url);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，在同一台机器上（单网卡），同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例，此时则调用 reset 方法重置服务器的一些配置。考虑到篇幅问题，关于服务器实例重置的代码就不分析了。接下来分析服务器实例的创建过程。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ExchangeServer <span class="title function_">createServer</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,</span><br><span class="line">    <span class="comment">// 添加心跳检测配置到 url 中</span></span><br><span class="line">    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));</span><br><span class="line">    <span class="comment">// 获取 server 参数，默认为 netty</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展，不存在则抛出异常</span></span><br><span class="line">    <span class="keyword">if</span> (str != <span class="literal">null</span> &amp;&amp; str.length() &gt; <span class="number">0</span> &amp;&amp; !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Unsupported server type: &quot;</span> + str + <span class="string">&quot;, url: &quot;</span> + url);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加编码解码器参数</span></span><br><span class="line">    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);</span><br><span class="line">    ExchangeServer server;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 创建 ExchangeServer</span></span><br><span class="line">        server = Exchangers.bind(url, requestHandler);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Fail to start server...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 client 参数，可指定 netty，mina</span></span><br><span class="line">    str = url.getParameter(Constants.CLIENT_KEY);</span><br><span class="line">    <span class="keyword">if</span> (str != <span class="literal">null</span> &amp;&amp; str.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 获取所有的 Transporter 实现类名称集合，比如 supportedTypes = [netty, mina]</span></span><br><span class="line">        Set&lt;String&gt; supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();</span><br><span class="line">        <span class="comment">// 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中，</span></span><br><span class="line">        <span class="comment">// 是否包含 client 所表示的 Transporter，若不包含，则抛出异常</span></span><br><span class="line">        <span class="keyword">if</span> (!supportedTypes.contains(str)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Unsupported client type...&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> server;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，createServer 包含三个核心的操作。第一是检测是否存在 server 参数所代表的 Transporter 拓展，不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展，不存在也是抛出异常。两次检测操作所对应的代码比较直白了，无需多说。但创建服务器的操作目前还不是很清晰，我们继续往下看。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExchangeServer <span class="title function_">bind</span><span class="params">(URL url, ExchangeHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (handler == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;handler == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    url = url.addParameterIfAbsent(Constants.CODEC_KEY, <span class="string">&quot;exchange&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取 Exchanger，默认为 HeaderExchanger。</span></span><br><span class="line">    <span class="comment">// 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例</span></span><br><span class="line">    <span class="keyword">return</span> getExchanger(url).bind(url, handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码比较简单，就不多说了。下面看一下 HeaderExchanger 的 bind 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ExchangeServer <span class="title function_">bind</span><span class="params">(URL url, ExchangeHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="comment">// 创建 HeaderExchangeServer 实例，该方法包含了多步操作，本别如下：</span></span><br><span class="line">    <span class="comment">//   1. new HeaderExchangeHandler(handler)</span></span><br><span class="line">    <span class="comment">//     2. new DecodeHandler(new HeaderExchangeHandler(handler))</span></span><br><span class="line">    <span class="comment">//   3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HeaderExchangeServer</span>(Transporters.bind(url, <span class="keyword">new</span> <span class="title class_">DecodeHandler</span>(<span class="keyword">new</span> <span class="title class_">HeaderExchangeHandler</span>(handler))));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>HeaderExchanger 的 bind 方法包含的逻辑比较多，但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Server <span class="title function_">bind</span><span class="params">(URL url, ChannelHandler... handlers)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (handlers == <span class="literal">null</span> || handlers.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;handlers == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    ChannelHandler handler;</span><br><span class="line">    <span class="keyword">if</span> (handlers.length == <span class="number">1</span>) &#123;</span><br><span class="line">        handler = handlers[<span class="number">0</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 如果 handlers 元素数量大于1，则创建 ChannelHandler 分发器</span></span><br><span class="line">        handler = <span class="keyword">new</span> <span class="title class_">ChannelHandlerDispatcher</span>(handlers);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取自适应 Transporter 实例，并调用实例方法</span></span><br><span class="line">    <span class="keyword">return</span> getTransporter().bind(url, handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，getTransporter() 方法获取的 Transporter 是在运行时动态创建的，类名为 TransporterAdaptive，也就是自适应拓展类。我在<a href="%5Bhttp://www.tianxiaobo.com/2018/10/13/Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95%E5%8E%9F%E7%90%86/%5D(http://www.tianxiaobo.com/2018/10/13/Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95%E5%8E%9F%E7%90%86/)">上一篇文章</a>中详细分析了自适应拓展类的生成过程，对自适应拓展类不了解的同学可以参考我之前的文章，这里不再赘述。TransporterAdaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter，默认为 NettyTransporter。下面我们继续跟下去，这次分析的是 NettyTransporter 的 bind 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Server <span class="title function_">bind</span><span class="params">(URL url, ChannelHandler listener)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="comment">// 创建 NettyServer</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">NettyServer</span>(url, listener);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里仅有一句创建 NettyServer 的代码，没啥好讲的，我们继续向下看。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyServer</span> <span class="keyword">extends</span> <span class="title class_">AbstractServer</span> <span class="keyword">implements</span> <span class="title class_">Server</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">NettyServer</span><span class="params">(URL url, ChannelHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">        <span class="comment">// 调用父类构造方法</span></span><br><span class="line">        <span class="built_in">super</span>(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractServer</span> <span class="keyword">extends</span> <span class="title class_">AbstractEndpoint</span> <span class="keyword">implements</span> <span class="title class_">Server</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AbstractServer</span><span class="params">(URL url, ChannelHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">        <span class="comment">// 调用父类构造方法，这里就不用跟进去了，没什么复杂逻辑</span></span><br><span class="line">        <span class="built_in">super</span>(url, handler);</span><br><span class="line">        localAddress = getUrl().toInetSocketAddress();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 ip 和端口</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">bindIp</span> <span class="operator">=</span> getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());</span><br><span class="line">        <span class="type">int</span> <span class="variable">bindPort</span> <span class="operator">=</span> getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());</span><br><span class="line">        <span class="keyword">if</span> (url.getParameter(Constants.ANYHOST_KEY, <span class="literal">false</span>) || NetUtils.isInvalidLocalHost(bindIp)) &#123;</span><br><span class="line">            <span class="comment">// 设置 ip 为 0.0.0.0</span></span><br><span class="line">            bindIp = NetUtils.ANYHOST;</span><br><span class="line">        &#125;</span><br><span class="line">        bindAddress = <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(bindIp, bindPort);</span><br><span class="line">        <span class="comment">// 获取最大可接受连接数</span></span><br><span class="line">        <span class="built_in">this</span>.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);</span><br><span class="line">        <span class="built_in">this</span>.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 调用模板方法 doOpen 启动服务器</span></span><br><span class="line">            doOpen();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RemotingException</span>(<span class="string">&quot;Failed to bind &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">DataStore</span> <span class="variable">dataStore</span> <span class="operator">=</span> ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();</span><br><span class="line">        executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">doOpen</span><span class="params">()</span> <span class="keyword">throws</span> Throwable;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">doClose</span><span class="params">()</span> <span class="keyword">throws</span> Throwable;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面多数代码为赋值代码，不需要多讲。我们重点关注 doOpen 抽象方法，该方法需要子类实现。下面回到 NettyServer 中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doOpen</span><span class="params">()</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">    NettyHelper.setNettyLoggerFactory();</span><br><span class="line">    <span class="comment">// 创建 boss 和 worker 线程池</span></span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">boss</span> <span class="operator">=</span> Executors.newCachedThreadPool(<span class="keyword">new</span> <span class="title class_">NamedThreadFactory</span>(<span class="string">&quot;NettyServerBoss&quot;</span>, <span class="literal">true</span>));</span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">worker</span> <span class="operator">=</span> Executors.newCachedThreadPool(<span class="keyword">new</span> <span class="title class_">NamedThreadFactory</span>(<span class="string">&quot;NettyServerWorker&quot;</span>, <span class="literal">true</span>));</span><br><span class="line">    <span class="type">ChannelFactory</span> <span class="variable">channelFactory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioServerSocketChannelFactory</span>(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建 ServerBootstrap</span></span><br><span class="line">    bootstrap = <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>(channelFactory);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">NettyHandler</span> <span class="variable">nettyHandler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyHandler</span>(getUrl(), <span class="built_in">this</span>);</span><br><span class="line">    channels = nettyHandler.getChannels();</span><br><span class="line">    bootstrap.setOption(<span class="string">&quot;child.tcpNoDelay&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="comment">// 设置 PipelineFactory</span></span><br><span class="line">    bootstrap.setPipelineFactory(<span class="keyword">new</span> <span class="title class_">ChannelPipelineFactory</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> ChannelPipeline <span class="title function_">getPipeline</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="type">NettyCodecAdapter</span> <span class="variable">adapter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyCodecAdapter</span>(getCodec(), getUrl(), NettyServer.<span class="built_in">this</span>);</span><br><span class="line">            <span class="type">ChannelPipeline</span> <span class="variable">pipeline</span> <span class="operator">=</span> Channels.pipeline();</span><br><span class="line">            pipeline.addLast(<span class="string">&quot;decoder&quot;</span>, adapter.getDecoder());</span><br><span class="line">            pipeline.addLast(<span class="string">&quot;encoder&quot;</span>, adapter.getEncoder());</span><br><span class="line">            pipeline.addLast(<span class="string">&quot;handler&quot;</span>, nettyHandler);</span><br><span class="line">            <span class="keyword">return</span> pipeline;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">// 绑定到指定的 ip 和端口上</span></span><br><span class="line">    channel = bootstrap.bind(getBindAddress());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上就是 NettyServer 创建的过程，dubbo 默认使用的 NettyServer 是基于 netty 3.x 版本实现的，比较老了。因此 Dubbo 中另外提供了 netty 4.x 版本的 NettyServer，大家可在使用 Dubbo 的过程中按需进行配置。</p><p>到此，关于服务导出的过程就分析完了。整个过程比较复杂，大家在分析的过程中耐心一些。并且多写 Demo 进行进行调试，以便能够更好的理解代码逻辑。好了，本节内容先到这里，接下来分析服务导出的另一块逻辑 – 服务注册。</p><h3 id="2-2-4-服务注册"><a href="#2-2-4-服务注册" class="headerlink" title="2.2.4 服务注册"></a>2.2.4 服务注册</h3><p>本节我们来分析服务注册过程，服务注册操作对于 Dubbo 来说不是必需的，通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做，直连方式不利于服务治理，仅推荐在测试环境测试服务时使用。对于 Dubbo 来说，注册中心虽不是必需，但却是必要的。因此，关于注册中心以及服务注册相关逻辑，我们也需要搞懂。</p><p>本节内容以 Zookeeper 注册中心作为分析目标，其他类型注册中心大家可自行分析。下面从服务注册的入口方法开始分析，我们把目光再次移到 RegistryProtocol 的 export 方法上。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Exporter&lt;T&gt; <span class="title function_">export</span><span class="params">(<span class="keyword">final</span> Invoker&lt;T&gt; originInvoker)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// $&#123;导出服务&#125;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略其他代码</span></span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">register</span> <span class="operator">=</span> registeredProviderUrl.getParameter(<span class="string">&quot;register&quot;</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">if</span> (register) &#123;</span><br><span class="line">        <span class="comment">// 注册服务</span></span><br><span class="line">        register(registryUrl, registeredProviderUrl);</span><br><span class="line">        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">URL</span> <span class="variable">overrideSubscribeUrl</span> <span class="operator">=</span> getSubscribedOverrideUrl(registeredProviderUrl);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">OverrideListener</span> <span class="variable">overrideSubscribeListener</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OverrideListener</span>(overrideSubscribeUrl, originInvoker);</span><br><span class="line">    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);</span><br><span class="line">    <span class="comment">// 订阅 override 数据</span></span><br><span class="line">    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部分代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RegistryProtocol 的 export 方法包含了服务导出，注册，以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了，本节将分析服务注册逻辑，数据订阅逻辑将在下一节进行分析。下面开始本节的分析，相关代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(URL registryUrl, URL registedProviderUrl)</span> &#123;</span><br><span class="line">    <span class="comment">// 获取 Registry</span></span><br><span class="line">    <span class="type">Registry</span> <span class="variable">registry</span> <span class="operator">=</span> registryFactory.getRegistry(registryUrl);</span><br><span class="line">    <span class="comment">// 注册服务</span></span><br><span class="line">    registry.register(registedProviderUrl);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>register 方法包含两步操作，第一步是获取注册中心实例，第二步是向注册中心注册服务。接下来，我分两节内容对这两步操作进行分析。按照顺序，先来分析获取注册中心的逻辑。</p><h4 id="2-2-4-1-创建注册中心"><a href="#2-2-4-1-创建注册中心" class="headerlink" title="2.2.4.1 创建注册中心"></a>2.2.4.1 创建注册中心</h4><p>本节内容以 Zookeeper 注册中心为例进行分析。下面先来看一下 getRegistry 方法的源码，这个方法由 AbstractRegistryFactory 实现。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Registry <span class="title function_">getRegistry</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    url = url.setPath(RegistryService.class.getName())</span><br><span class="line">            .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())</span><br><span class="line">            .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> url.toServiceString();</span><br><span class="line">    LOCK.lock();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 访问缓存</span></span><br><span class="line">        <span class="type">Registry</span> <span class="variable">registry</span> <span class="operator">=</span> REGISTRIES.get(key);</span><br><span class="line">        <span class="keyword">if</span> (registry != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> registry;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 缓存未命中，创建 Registry 实例</span></span><br><span class="line">        registry = createRegistry(url);</span><br><span class="line">        <span class="keyword">if</span> (registry == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Can not create registry...&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 写入缓存</span></span><br><span class="line">        REGISTRIES.put(key, registry);</span><br><span class="line">        <span class="keyword">return</span> registry;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        LOCK.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">abstract</span> Registry <span class="title function_">createRegistry</span><span class="params">(URL url)</span>;</span><br></pre></td></tr></table></figure><p>如上，getRegistry 方法先访问缓存，缓存未命中则调用 createRegistry 创建 Registry，然后写入缓存。这里的 createRegistry 是一个模板方法，由具体的子类实现。因此，下面我们到 ZookeeperRegistryFactory 中探究一番。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ZookeeperRegistryFactory</span> <span class="keyword">extends</span> <span class="title class_">AbstractRegistryFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// zookeeperTransporter 由 SPI 在运行时注入，类型为 ZookeeperTransporter$Adaptive</span></span><br><span class="line">    <span class="keyword">private</span> ZookeeperTransporter zookeeperTransporter;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setZookeeperTransporter</span><span class="params">(ZookeeperTransporter zookeeperTransporter)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.zookeeperTransporter = zookeeperTransporter;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Registry <span class="title function_">createRegistry</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建 ZookeeperRegistry</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ZookeeperRegistry</span>(url, zookeeperTransporter);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ZookeeperRegistryFactory 的 createRegistry 方法仅包含一句代码，无需解释，继续跟下去。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ZookeeperRegistry</span><span class="params">(URL url, ZookeeperTransporter zookeeperTransporter)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(url);</span><br><span class="line">    <span class="keyword">if</span> (url.isAnyHost()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;registry address == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取组名，默认为 dubbo</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">group</span> <span class="operator">=</span> url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);</span><br><span class="line">    <span class="keyword">if</span> (!group.startsWith(Constants.PATH_SEPARATOR)) &#123;</span><br><span class="line">        <span class="comment">// group = &quot;/&quot; + group</span></span><br><span class="line">        group = Constants.PATH_SEPARATOR + group;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.root = group;</span><br><span class="line">    <span class="comment">// 创建 Zookeeper 客户端，默认为 CuratorZookeeperTransporter</span></span><br><span class="line">    zkClient = zookeeperTransporter.connect(url);</span><br><span class="line">    <span class="comment">// 添加状态监听器</span></span><br><span class="line">    zkClient.addStateListener(<span class="keyword">new</span> <span class="title class_">StateListener</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">stateChanged</span><span class="params">(<span class="type">int</span> state)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (state == RECONNECTED) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    recover();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    logger.error(e.getMessage(), e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上面的代码代码中，我们重点关注 ZookeeperTransporter 的 connect 方法调用，这个方法用于创建 Zookeeper 客户端。创建好 Zookeeper 客户端，意味着注册中心的创建过程就结束了。不过，显然我们不能就此停止，难道大家没有兴趣了解一下 Zookeeper 客户端的创建过程吗？如果有，那么继续向下看。没有的话，直接跳到下一节。那我接着分析了。</p><p>前面说过，这里的 zookeeperTransporter 类型为自适应拓展类，因此 connect 方法会在被调用时决定加载什么类型的 ZookeeperTransporter 拓展，默认为 CuratorZookeeperTransporter。下面我们到 CuratorZookeeperTransporter 中看一看。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ZookeeperClient <span class="title function_">connect</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="comment">// 创建 CuratorZookeeperClient</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CuratorZookeeperClient</span>(url);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面方法仅用于创建 CuratorZookeeperClient 实例，没什么好说的，继续往下看。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CuratorZookeeperClient</span> <span class="keyword">extends</span> <span class="title class_">AbstractZookeeperClient</span>&lt;CuratorWatcher&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CuratorFramework client;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CuratorZookeeperClient</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(url);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 创建 CuratorFramework 构造器</span></span><br><span class="line">            CuratorFrameworkFactory.<span class="type">Builder</span> <span class="variable">builder</span> <span class="operator">=</span> CuratorFrameworkFactory.builder()</span><br><span class="line">                    .connectString(url.getBackupAddress())</span><br><span class="line">                    .retryPolicy(<span class="keyword">new</span> <span class="title class_">RetryNTimes</span>(<span class="number">1</span>, <span class="number">1000</span>))</span><br><span class="line">                    .connectionTimeoutMs(<span class="number">5000</span>);</span><br><span class="line">            <span class="type">String</span> <span class="variable">authority</span> <span class="operator">=</span> url.getAuthority();</span><br><span class="line">            <span class="keyword">if</span> (authority != <span class="literal">null</span> &amp;&amp; authority.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                builder = builder.authorization(<span class="string">&quot;digest&quot;</span>, authority.getBytes());</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 构建 CuratorFramework 实例</span></span><br><span class="line">            client = builder.build();</span><br><span class="line">            <span class="comment">// 添加监听器</span></span><br><span class="line">            client.getConnectionStateListenable().addListener(<span class="keyword">new</span> <span class="title class_">ConnectionStateListener</span>() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">stateChanged</span><span class="params">(CuratorFramework client, ConnectionState state)</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (state == ConnectionState.LOST) &#123;</span><br><span class="line">                        CuratorZookeeperClient.<span class="built_in">this</span>.stateChanged(StateListener.DISCONNECTED);</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (state == ConnectionState.CONNECTED) &#123;</span><br><span class="line">                        CuratorZookeeperClient.<span class="built_in">this</span>.stateChanged(StateListener.CONNECTED);</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (state == ConnectionState.RECONNECTED) &#123;</span><br><span class="line">                        CuratorZookeeperClient.<span class="built_in">this</span>.stateChanged(StateListener.RECONNECTED);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 启动客户端</span></span><br><span class="line">            client.start();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e.getMessage(), e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CuratorZookeeperClient 构造方法主要用于创建和启动 CuratorFramework 实例。以上基本上都是 Curator 框架的代码，大家如果对 Curator 框架不是很了解，可以参考 Curator 官方文档，并写点 Demo 跑跑。</p><p>本节分析了 ZookeeperRegistry 实例的创建过程，整个过程并不是很复杂。大家在看完分析后，可以自行调试，以加深印象。现在注册中心实例创建好了，接下来要做的事情是向注册中心注册服务，我们继续往下看。</p><h4 id="2-2-4-2-节点创建"><a href="#2-2-4-2-节点创建" class="headerlink" title="2.2.4.2 节点创建"></a>2.2.4.2 节点创建</h4><p>以 Zookeeper 为例，所谓的服务注册，本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。为了验证这个说法，下面我们将 Dobbo 官方提供提供的实例跑起来，然后通过 Zookeeper 可视化客户端 <a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/zookeeper/tree/b79af153d0f98a4f3f3516910ed47234d7b3d74e/src/contrib/zooinspector">ZooInspector</a> 查看节点数据。</p><p>可以看到 com.alibaba.dubbo.demo.DemoService 这个服务对应的配置信息（存储在 URL 中）最终被注册到了 &#x2F;dubbo&#x2F;com.alibaba.dubbo.demo.DemoService&#x2F;providers&#x2F; 节点下。搞懂了服务注册的本质，那么接下来我们就可以去阅读服务注册的代码了。服务注册的接口为 register(URL)，这个方法定义在 FailbackRegistry 抽象类中。方法代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.register(url);</span><br><span class="line">    failedRegistered.remove(url);</span><br><span class="line">    failedUnregistered.remove(url);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 模板方法，由子类实现</span></span><br><span class="line">        doRegister(url);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="type">Throwable</span> <span class="variable">t</span> <span class="operator">=</span> e;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 check 参数，若 check = true 将会直接抛出异常</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">check</span> <span class="operator">=</span> getUrl().getParameter(Constants.CHECK_KEY, <span class="literal">true</span>)</span><br><span class="line">                &amp;&amp; url.getParameter(Constants.CHECK_KEY, <span class="literal">true</span>)</span><br><span class="line">                &amp;&amp; !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">skipFailback</span> <span class="operator">=</span> t <span class="keyword">instanceof</span> SkipFailbackWrapperException;</span><br><span class="line">        <span class="keyword">if</span> (check || skipFailback) &#123;</span><br><span class="line">            <span class="keyword">if</span> (skipFailback) &#123;</span><br><span class="line">                t = t.getCause();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Failed to register&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;Failed to register&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 记录注册失败的链接</span></span><br><span class="line">        failedRegistered.add(url);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">doRegister</span><span class="params">(URL url)</span>;</span><br></pre></td></tr></table></figure><p>如上，我们重点关注 doRegister 方法调用即可，其他的代码先忽略。doRegister 方法是一个模板方法，因此我们到 FailbackRegistry 子类 ZookeeperRegistry 中进行分析。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">doRegister</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 通过 Zookeeper 客户端创建节点，节点路径由 toUrlPath 方法生成，路径格式如下:</span></span><br><span class="line">        <span class="comment">//   /$&#123;group&#125;/$&#123;serviceInterface&#125;/providers/$&#123;url&#125;</span></span><br><span class="line">        <span class="comment">// 比如</span></span><br><span class="line">        <span class="comment">//   /dubbo/com.tianxiaobo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......</span></span><br><span class="line">        zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, <span class="literal">true</span>));</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Failed to register...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成，该方法逻辑不难理解，就不分析了。接下来分析 create 方法，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">create</span><span class="params">(String path, <span class="type">boolean</span> ephemeral)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!ephemeral) &#123;</span><br><span class="line">        <span class="comment">// 如果要创建的节点类型非临时节点，那么这里要检测节点是否存在</span></span><br><span class="line">        <span class="keyword">if</span> (checkExists(path)) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> path.lastIndexOf(<span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        create(path.substring(<span class="number">0</span>, i), <span class="literal">false</span>);    <span class="comment">// 递归创建上一级路径</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据 ephemeral 的值创建临时或持久节点</span></span><br><span class="line">    <span class="keyword">if</span> (ephemeral) &#123;</span><br><span class="line">        createEphemeral(path);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        createPersistent(path);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面方法先是通过递归创建当前节点的上一级路径，然后再根据 ephemeral 的值决定创建临时还是持久节点。createEphemeral 和 createPersistent 这两个方法都比较简单，这里简单分析其中的一个。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">createEphemeral</span><span class="params">(String path)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 通过 Curator 框架创建节点</span></span><br><span class="line">        client.create().withMode(CreateMode.EPHEMERAL).forPath(path);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (NodeExistsException e) &#123;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e.getMessage(), e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好了，到此关于服务注册的过程就分析完了。整个过程可简单总结为：先创建注册中心实例，之后再通过注册中心实例注册服务。本节先到这，接下来分析数据订阅过程。</p><h3 id="2-2-5-订阅-override-数据"><a href="#2-2-5-订阅-override-数据" class="headerlink" title="2.2.5 订阅 override 数据"></a>2.2.5 订阅 override 数据</h3><p>订阅 override 数据对应的代码我粗略看了一遍，这部分代码的主要目的是为了在服务配置发生变化时，重新导出服务。具体的使用场景应该当我们通过 Dubbo 管理后台修改了服务配置后，Dubbo 得到服务配置被修改的通知，然后重新导出服务。这个使用场景只是猜测，我并未进行过验证。如果大家有兴趣可以自行验证。</p><p>override 数据订阅相关代码也不是很少，考虑到文章篇幅问题以及重要性，遂决定不对此逻辑进行详细的分析。如果大家有兴趣，可自行分析。</p><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3.总结"></a>3.总结</h2><p>本篇文章详细分析了 Dubbo 服务导出过程，包括配置检测，URL 组装，Invoker 创建过程、导出服务以及注册服务等等。篇幅比较大，需要大家耐心阅读。对于这篇文章，我建议大家当成一个工具书使用。需要的时候跳到指定章节看一下，通读可能会有点累。由于文章篇幅比较大，因此可能会隐藏一些我没意识到的错误。若大家在阅读的过程中发现了错误，还请指出。如果能够不吝赐教，那就更好了，先在这里说声谢谢。</p></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/bc8dae89.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/bc8dae89.html")'>Dubbo 源码分析 – 服务导出</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/bc8dae89.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=Dubbo 源码分析 – 服务导出&amp;url=https://it985.github.io/posts/bc8dae89.html&amp;pic=https://www.bing.com/th?id=OHR.SealRiver_EN-IN3503931734_UHD.jpg" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/cf451fe.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.FrankensteinFriday_EN-IN2387604310_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Dubbo 源码分析 – 自适应拓展原理</div></div></a></div><div class="next-post pull-right"><a href="/posts/d72547db.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.YiPeng_EN-GB1679352084_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Dubbo 源码分析 – 服务引用</div></div></a></div></nav><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%B8%89%E3%80%81Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E2%80%93-%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA"><span class="toc-number">1.</span> <span class="toc-text">三、Dubbo 源码分析 – 服务导出</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E6%9C%8D%E5%8A%A1%E5%AF%BC%E5%87%BA%E8%BF%87%E7%A8%8B"><span class="toc-number">1.1.</span> <span class="toc-text">1.服务导出过程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">1.2.</span> <span class="toc-text">2.源码分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-%E5%89%8D%E7%BD%AE%E5%B7%A5%E4%BD%9C"><span class="toc-number">1.2.1.</span> <span class="toc-text">2.1 前置工作</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-1-1-%E6%A3%80%E6%9F%A5%E9%85%8D%E7%BD%AE"><span class="toc-number">1.2.1.1.</span> <span class="toc-text">2.1.1 检查配置</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-1-2-%E5%A4%9A%E5%8D%8F%E8%AE%AE%E5%A4%9A%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E5%AF%BC%E5%87%BA%E6%9C%8D%E5%8A%A1"><span class="toc-number">1.2.1.2.</span> <span class="toc-text">2.1.2 多协议多注册中心导出服务</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-1-3-%E7%BB%84%E8%A3%85-URL"><span class="toc-number">1.2.1.3.</span> <span class="toc-text">2.1.3 组装 URL</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-%E5%AF%BC%E5%87%BA-Dubbo-%E6%9C%8D%E5%8A%A1"><span class="toc-number">1.2.2.</span> <span class="toc-text">2.2 导出 Dubbo 服务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-1-Invoker-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B"><span class="toc-number">1.2.3.</span> <span class="toc-text">2.2.1 Invoker 创建过程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-2-%E5%AF%BC%E5%87%BA%E6%9C%8D%E5%8A%A1%E5%88%B0%E6%9C%AC%E5%9C%B0"><span class="toc-number">1.2.4.</span> <span class="toc-text">2.2.2 导出服务到本地</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-3-%E5%AF%BC%E5%87%BA%E6%9C%8D%E5%8A%A1%E5%88%B0%E8%BF%9C%E7%A8%8B"><span class="toc-number">1.2.5.</span> <span class="toc-text">2.2.3 导出服务到远程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-4-%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C"><span class="toc-number">1.2.6.</span> <span class="toc-text">2.2.4 服务注册</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-4-1-%E5%88%9B%E5%BB%BA%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83"><span class="toc-number">1.2.6.1.</span> <span class="toc-text">2.2.4.1 创建注册中心</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-4-2-%E8%8A%82%E7%82%B9%E5%88%9B%E5%BB%BA"><span class="toc-number">1.2.6.2.</span> <span class="toc-text">2.2.4.2 节点创建</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-5-%E8%AE%A2%E9%98%85-override-%E6%95%B0%E6%8D%AE"><span class="toc-number">1.2.7.</span> <span class="toc-text">2.2.5 订阅 override 数据</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E6%80%BB%E7%BB%93"><span class="toc-number">1.3.</span> <span class="toc-text">3.总结</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>